diff --git a/client/constants.js b/client/constants.js index cf1ef18431..5f04e83980 100644 --- a/client/constants.js +++ b/client/constants.js @@ -33,8 +33,13 @@ export const RESET_PROJECT = 'RESET_PROJECT'; export const SET_PROJECT = 'SET_PROJECT'; export const SET_PROJECTS = 'SET_PROJECTS'; +// <<<<<<< HEAD +export const SET_COLLECTIONS_FOR_COLLECTION_LIST = + 'SET_COLLECTIONS_FOR_COLLECTION_LIST'; +// ======= export const SET_PROJECTS_FOR_COLLECTION_LIST = 'SET_PROJECTS_FOR_COLLECTION_LIST'; +// >>>>>>> develop-codemirror-v6 export const SET_COLLECTIONS = 'SET_COLLECTIONS'; export const CREATE_COLLECTION = 'CREATED_COLLECTION'; diff --git a/client/modules/IDE/actions/collections.js b/client/modules/IDE/actions/collections.js index 68a42f500d..3f2049d307 100644 --- a/client/modules/IDE/actions/collections.js +++ b/client/modules/IDE/actions/collections.js @@ -6,33 +6,33 @@ import { setToastText, showToast } from './toast'; const TOAST_DISPLAY_TIME_MS = 1500; -export function getCollections(username) { - return (dispatch) => { - dispatch(startLoader()); - let url; - if (username) { - url = `/${username}/collections`; - } else { - url = '/collections'; - } - return apiClient - .get(url) - .then((response) => { - dispatch({ - type: ActionTypes.SET_COLLECTIONS, - collections: response.data - }); - dispatch(stopLoader()); - }) - .catch((error) => { - dispatch({ - type: ActionTypes.ERROR, - error: error?.response?.data - }); - dispatch(stopLoader()); - }); - }; -} +// export function getCollections(username) { +// return (dispatch) => { +// dispatch(startLoader()); +// let url; +// if (username) { +// url = `/${username}/collections`; +// } else { +// url = '/collections'; +// } +// return apiClient +// .get(url) +// .then((response) => { +// dispatch({ +// type: ActionTypes.SET_COLLECTIONS, +// collections: response.data +// }); +// dispatch(stopLoader()); +// }) +// .catch((error) => { +// dispatch({ +// type: ActionTypes.ERROR, +// error: error?.response?.data +// }); +// dispatch(stopLoader()); +// }); +// }; +// } export function createCollection(collection) { return (dispatch) => { @@ -167,3 +167,60 @@ export function deleteCollection(collectionId) { }); }; } +const buildCollectionUrl = (username, options = {}) => { + const { + page = 1, + limit = 10, + sortField = 'updatedAt', + sortDir = 'desc', + q = '' + } = options; + + const base = username + ? `/${encodeURIComponent(username)}/collections` + : '/collections'; + + const params = new URLSearchParams({ + page: String(page), + limit: String(limit), + sortField, + sortDir + }); + + const trimmed = q.trim(); + + if (trimmed) { + params.set('q', trimmed); + } + + return `${base}?${params.toString()}`; +}; + +const fetchCollections = (username, options, successType) => (dispatch) => { + dispatch(startLoader()); + + const url = buildCollectionUrl(username, options); + + return apiClient + .get(url) + .then((response) => { + dispatch({ type: successType, collections: response.data }); + dispatch(stopLoader()); + return response.data; + }) + .catch((error) => { + dispatch({ type: ActionTypes.ERROR, error: error?.response?.data }); + dispatch(stopLoader()); + throw error; + }); +}; + +export const getCollections = (username, options) => + fetchCollections(username, options, ActionTypes.SET_COLLECTIONS); + +export const getCollectionsForCollectionList = (username, options) => + fetchCollections( + username, + options, + ActionTypes.SET_COLLECTIONS_FOR_COLLECTION_LIST + ); diff --git a/client/modules/IDE/actions/ide.js b/client/modules/IDE/actions/ide.js index 80a43443bc..ad16cd9a5d 100644 --- a/client/modules/IDE/actions/ide.js +++ b/client/modules/IDE/actions/ide.js @@ -236,23 +236,10 @@ export function hideErrorModal() { }; } -export function hideRuntimeErrorWarning() { - return { - type: ActionTypes.HIDE_RUNTIME_ERROR_WARNING - }; -} - -export function showRuntimeErrorWarning() { - return { - type: ActionTypes.SHOW_RUNTIME_ERROR_WARNING - }; -} - export function startSketch() { return (dispatch, getState) => { dispatch(clearConsole()); dispatch(startVisualSketch()); - dispatch(showRuntimeErrorWarning()); const state = getState(); dispatchMessage({ type: MessageTypes.SKETCH, diff --git a/client/modules/IDE/components/AddToCollectionList.jsx b/client/modules/IDE/components/AddToCollectionList.jsx index 75713367ee..f3910ab4fc 100644 --- a/client/modules/IDE/components/AddToCollectionList.jsx +++ b/client/modules/IDE/components/AddToCollectionList.jsx @@ -4,15 +4,13 @@ import { Helmet } from 'react-helmet'; import { useTranslation } from 'react-i18next'; import { useDispatch, useSelector } from 'react-redux'; import styled from 'styled-components'; +import { addToCollection, removeFromCollection } from '../actions/collections'; +import { getCollectionsForCollectionList } from '../actions/collections'; import { Loader } from '../../App/components/Loader'; -import { - addToCollection, - getCollections, - removeFromCollection -} from '../actions/collections'; import getSortedCollections from '../selectors/collections'; import QuickAddList from './QuickAddList'; import { remSize } from '../../../theme'; +import Pagination from './Pagination'; export const CollectionAddSketchWrapper = styled.div` width: ${remSize(600)}; @@ -34,7 +32,19 @@ const AddToCollectionList = ({ projectId }) => { const username = useSelector((state) => state.user.username); - const collections = useSelector(getSortedCollections); + const collections = useSelector( + (state) => state.collectionsListCollections.collections + ); + + const paginationMeta = useSelector( + (state) => state.collectionsListCollections.metadata + ); + + const q = useSelector((state) => state.search.collectionSearchTerm); + const hasCollections = () => collections?.length > 0; + + const [page, setPage] = useState(1); + const limit = 10; // TODO: improve loading state const loading = useSelector((state) => state.loading); @@ -42,8 +52,18 @@ const AddToCollectionList = ({ projectId }) => { const showLoader = loading && !hasLoadedData; useEffect(() => { - dispatch(getCollections(username)).then(() => setHasLoadedData(true)); - }, [dispatch, username]); + dispatch( + getCollectionsForCollectionList(username, { + page, + limit, + q + }) + ).finally(() => setHasLoadedData(true)); + }, [dispatch, username, page, q]); + + useEffect(() => { + setPage(1); + }, [q]); const handleCollectionAdd = (collection) => { dispatch(addToCollection(collection.id, projectId)); @@ -66,11 +86,23 @@ const AddToCollectionList = ({ projectId }) => { return t('AddToCollectionList.Empty'); } return ( - + <> + + {hasCollections() && ( + + )} + ); }; diff --git a/client/modules/IDE/components/AddToCollectionSketchList.jsx b/client/modules/IDE/components/AddToCollectionSketchList.jsx index 29937a6cd0..be4cac59b6 100644 --- a/client/modules/IDE/components/AddToCollectionSketchList.jsx +++ b/client/modules/IDE/components/AddToCollectionSketchList.jsx @@ -19,7 +19,6 @@ const AddToCollectionSketchList = ({ collection }) => { const dispatch = useDispatch(); const username = useSelector((state) => state.user.username); - const sketches = useSelector( (state) => state.collectionsListProjects.projects ); @@ -34,8 +33,6 @@ const AddToCollectionSketchList = ({ collection }) => { const [page, setPage] = useState(1); const limit = 10; - - // TODO: improve loading state const loading = useSelector((state) => state.loading); const [hasLoadedData, setHasLoadedData] = useState(false); const showLoader = loading && !hasLoadedData; diff --git a/client/modules/IDE/components/CollectionList/CollectionList.jsx b/client/modules/IDE/components/CollectionList/CollectionList.jsx index 9a59c02d8a..dfad473191 100644 --- a/client/modules/IDE/components/CollectionList/CollectionList.jsx +++ b/client/modules/IDE/components/CollectionList/CollectionList.jsx @@ -21,13 +21,17 @@ import CollectionListRow from './CollectionListRow'; import ArrowUpIcon from '../../../../images/sort-arrow-up.svg'; import ArrowDownIcon from '../../../../images/sort-arrow-down.svg'; +import Pagination from '../Pagination'; const CollectionList = ({ user, projectId, getCollections, + getCollectionsForCollectionList, getProject, collections, + search, + paginationMeta, username: propsUsername, loading, toggleDirectionForField, @@ -36,6 +40,8 @@ const CollectionList = ({ project, mobile }) => { + const [page, setPage] = useState(1); + const limit = mobile ? 7 : 10; const { t } = useTranslation(); const [hasLoadedData, setHasLoadedData] = useState(false); const [ @@ -43,13 +49,35 @@ const CollectionList = ({ setAddingSketchesToCollectionId ] = useState(null); + const sortField = sorting.field || 'updatedAt'; + const sortDir = + sorting.direction === SortingActions.DIRECTION.ASC ? 'asc' : 'desc'; + useEffect(() => { - if (projectId) { - getProject(projectId); - } - getCollections(propsUsername || user.username); resetSorting(); - }, []); + }, [user.username, resetSorting]); + + useEffect(() => { + setPage(1); + }, [user.username, search, sortField, sortDir]); + + useEffect(() => { + getCollectionsForCollectionList(user.username, { + page, + limit, + sortField, + sortDir, + q: search + }); + }, [ + getCollectionsForCollectionList, + user.username, + page, + limit, + sortField, + sortDir, + search + ]); useEffect(() => { if (!loading) { @@ -74,8 +102,7 @@ const CollectionList = ({ setAddingSketchesToCollectionId(null); }; - const hasCollections = () => - (!loading || hasLoadedData) && collections.length > 0; + const hasCollections = () => collections.length > 0; const renderLoader = () => { if (loading && !hasLoadedData) return ; @@ -150,70 +177,83 @@ const CollectionList = ({ }; return ( -
- - {getTitle} - + <> +
+ + {getTitle} + - {renderLoader()} - {renderEmptyTable()} + {renderLoader()} + {renderEmptyTable()} + {hasCollections() && ( + + + + {renderFieldHeader('name', t('CollectionList.HeaderName'))} + {renderFieldHeader( + 'createdAt', + t('CollectionList.HeaderCreatedAt', { + context: mobile ? 'mobile' : '' + }) + )} + {renderFieldHeader( + 'updatedAt', + t('CollectionList.HeaderUpdatedAt', { + context: mobile ? 'mobile' : '' + }) + )} + {renderFieldHeader( + 'numItems', + t('CollectionList.HeaderNumItems', { + context: mobile ? 'mobile' : '' + }) + )} + + + + + {collections.map((collection) => ( + showAddSketches(collection.id)} + /> + ))} + +
+ )} + {addingSketchesToCollectionId && ( + } + closeOverlay={hideAddSketches} + isFixedHeight + > + + + )} +
{hasCollections() && ( - - - - {renderFieldHeader('name', t('CollectionList.HeaderName'))} - {renderFieldHeader( - 'createdAt', - t('CollectionList.HeaderCreatedAt', { - context: mobile ? 'mobile' : '' - }) - )} - {renderFieldHeader( - 'updatedAt', - t('CollectionList.HeaderUpdatedAt', { - context: mobile ? 'mobile' : '' - }) - )} - {renderFieldHeader( - 'numItems', - t('CollectionList.HeaderNumItems', { - context: mobile ? 'mobile' : '' - }) - )} - - - - - {collections.map((collection) => ( - showAddSketches(collection.id)} - /> - ))} - -
- )} - {addingSketchesToCollectionId && ( - } - closeOverlay={hideAddSketches} - isFixedHeight - > - - + )} -
+ ); }; @@ -222,8 +262,16 @@ CollectionList.propTypes = { username: PropTypes.string, authenticated: PropTypes.bool.isRequired }).isRequired, + paginationMeta: PropTypes.shape({ + page: PropTypes.number.isRequired, + totalPages: PropTypes.number.isRequired, + totalCollections: PropTypes.number.isRequired, + limit: PropTypes.number.isRequired, + hasPagination: PropTypes.bool.isRequired + }).isRequired, projectId: PropTypes.string, getCollections: PropTypes.func.isRequired, + getCollectionsForCollectionList: PropTypes.func.isRequired, getProject: PropTypes.func.isRequired, collections: PropTypes.arrayOf( PropTypes.shape({ @@ -242,6 +290,7 @@ CollectionList.propTypes = { field: PropTypes.string.isRequired, direction: PropTypes.string.isRequired }).isRequired, + search: PropTypes.string.isRequired, project: PropTypes.shape({ id: PropTypes.string, owner: PropTypes.shape({ @@ -264,9 +313,18 @@ CollectionList.defaultProps = { function mapStateToProps(state, ownProps) { return { user: state.user, - collections: getSortedCollections(state), + collections: state.collectionsListCollections.collections ?? [], + paginationMeta: state.collectionsListCollections.metadata ?? { + page: 1, + totalPages: 1, + totalCollections: 0, + limit: 10, + hasPagination: true + }, + sorting: state.sorting, loading: state.loading, + search: state.search.collectionSearchTerm, project: state.project, projectId: ownProps && ownProps.params ? ownProps.params.project_id : null }; diff --git a/client/modules/IDE/components/ConsoleInput.jsx b/client/modules/IDE/components/ConsoleInput.jsx index 3806db0381..41fc729d4b 100644 --- a/client/modules/IDE/components/ConsoleInput.jsx +++ b/client/modules/IDE/components/ConsoleInput.jsx @@ -1,6 +1,16 @@ import PropTypes from 'prop-types'; -import React, { useRef, useEffect, useState } from 'react'; -import CodeMirror from 'codemirror'; +import React, { useRef, useEffect } from 'react'; +import { EditorState } from '@codemirror/state'; +import { EditorView, highlightSpecialChars, keymap } from '@codemirror/view'; +import { + bracketMatching, + syntaxHighlighting, + defaultHighlightStyle +} from '@codemirror/language'; +import { closeBrackets, closeBracketsKeymap } from '@codemirror/autocomplete'; +import { defaultKeymap, history, historyKeymap } from '@codemirror/commands'; +import { javascript } from '@codemirror/lang-javascript'; + import { useDispatch } from 'react-redux'; import { Encode } from 'console-feed'; @@ -11,31 +21,24 @@ import { dispatchMessage, MessageTypes } from '../../../utils/dispatcher'; // heavily inspired by // https://github.com/codesandbox/codesandbox-client/blob/92a1131f4ded6f7d9c16945dc7c18aa97c8ada27/packages/app/src/app/components/Preview/DevTools/Console/Input/index.tsx +// TODO(connie): Add theme support? function ConsoleInput({ theme, fontSize }) { - const [commandHistory, setCommandHistory] = useState([]); - const [commandCursor, setCommandCursor] = useState(-1); + const commandHistory = useRef([]); + const commandCursor = useRef(-1); const codemirrorContainer = useRef(null); - const cmInstance = useRef(null); + const cmView = useRef(null); const dispatch = useDispatch(); useEffect(() => { - cmInstance.current = CodeMirror(codemirrorContainer.current, { - theme: `p5-${theme}`, - scrollbarStyle: null, - keymap: 'sublime', - mode: 'javascript', - inputStyle: 'contenteditable' - }); - }, []); - - useEffect(() => { - const handleEnterKey = (cm, e) => { - if (e.key === 'Enter' && !e.shiftKey) { - e.preventDefault(); - e.stopPropagation(); - - const value = cm.getValue().trim(); - if (value === '') return; + const enterKeymap = { + key: 'Enter', + shiftKey: () => false, // Treat like a normal Enter key press if the Shift key is held down. + preventDefault: true, + stopPropogation: true, + run: (view) => { + const value = view.state.doc.toString().trim(); + if (value === '' || view.state.selection.main.empty === false) + return false; const messages = [ { log: Encode({ method: 'command', data: [value] }) } @@ -48,77 +51,91 @@ function ConsoleInput({ theme, fontSize }) { }); dispatch(dispatchConsoleEvent(consoleEvent)); - cm.setValue(''); - setCommandHistory([value, ...commandHistory]); - setCommandCursor(-1); - } - }; - - if (cmInstance.current) { - cmInstance.current.on('keydown', handleEnterKey); - } - - return () => { - if (cmInstance.current) { - cmInstance.current.off('keydown', handleEnterKey); + view.dispatch({ + changes: { from: 0, to: view.state.doc.length, insert: '' } + }); + commandHistory.current.unshift(value); + commandCursor.current = -1; + return true; } }; - }, [commandHistory]); - useEffect(() => { - const handleUpArrowKey = (cm, e) => { - if (e.key === 'ArrowUp') { - const lineNumber = cm.getDoc().getCursor().line; - if (lineNumber !== 0) return; + const upArrowKeymap = { + key: 'ArrowUp', + run: (view) => { + // Just let the cursor go up if we have a multiline input + // and the cursor isn't at the first line. + const currentLine = view.state.doc.lineAt( + view.state.selection.main.head + ).number; + // CM lines are 1-indexed, so the first line is 1. + if (currentLine > 1) return false; const newCursor = Math.min( - commandCursor + 1, - commandHistory.length - 1 + commandCursor.current + 1, + commandHistory.current.length - 1 ); - cm.setValue(commandHistory[newCursor] || ''); - const cursorPos = cm.getDoc().getLine(0).length - 1; - cm.getDoc().setCursor({ line: 0, ch: cursorPos }); - setCommandCursor(newCursor); - } - }; - - if (cmInstance.current) { - cmInstance.current.on('keydown', handleUpArrowKey); - } - - return () => { - if (cmInstance.current) { - cmInstance.current.off('keydown', handleUpArrowKey); + const newValue = commandHistory.current[newCursor] || ''; + view.dispatch({ + changes: { from: 0, to: view.state.doc.length, insert: newValue } + }); + const newCursorPos = newValue.length; + view.dispatch({ + selection: { anchor: newCursorPos, head: newCursorPos } + }); + commandCursor.current = newCursor; + return true; } }; - }, [commandCursor, commandHistory]); - useEffect(() => { - const handleArrowDownKey = (cm, e) => { - if (e.key === 'ArrowDown') { - const lineNumber = cm.getDoc().getCursor().line; - const lineCount = cm.lineCount(); - if (lineNumber + 1 !== lineCount) return; - - const newCursor = Math.max(commandCursor - 1, -1); - cm.setValue(commandHistory[newCursor] || ''); - const newLine = cm.getDoc().getLine(lineCount - 1); - const cursorPos = newLine ? newLine.length - 1 : 1; - cm.getDoc().setCursor({ line: lineCount - 1, ch: cursorPos }); - setCommandCursor(newCursor); + const downArrowKeymap = { + key: 'ArrowDown', + run: (view) => { + // Just let the cursor go down if we have a multiline input + // and the cursor isn't at the last line. + const currentLine = view.state.doc.lineAt( + view.state.selection.main.head + ).number; + const docLength = view.state.doc.lines; + if (currentLine !== docLength) return false; + + const newCursor = Math.max(commandCursor.current - 1, -1); + const newValue = commandHistory.current[newCursor] || ''; + view.dispatch({ + changes: { from: 0, to: view.state.doc.length, insert: newValue } + }); + const newCursorPos = newValue.length; + view.dispatch({ + selection: { anchor: newCursorPos, head: newCursorPos } + }); + commandCursor.current = newCursor; + return true; } }; - if (cmInstance.current) { - cmInstance.current.on('keydown', handleArrowDownKey); - } - - return () => { - if (cmInstance.current) { - cmInstance.current.off('keydown', handleArrowDownKey); - } - }; - }, [commandCursor, commandHistory]); + const cmState = EditorState.create({ + extensions: [ + history(), + highlightSpecialChars(), + bracketMatching(), + closeBrackets(), + syntaxHighlighting(defaultHighlightStyle), + javascript(), + keymap.of([ + enterKeymap, + upArrowKeymap, + downArrowKeymap, + ...defaultKeymap, + ...closeBracketsKeymap, + ...historyKeymap + ]) + ] + }); + cmView.current = new EditorView({ + state: cmState, + parent: codemirrorContainer.current + }); + }, []); return (
diff --git a/client/modules/IDE/components/Editor/codemirror.js b/client/modules/IDE/components/Editor/codemirror.js new file mode 100644 index 0000000000..3b122aa6af --- /dev/null +++ b/client/modules/IDE/components/Editor/codemirror.js @@ -0,0 +1,216 @@ +import { useRef, useEffect } from 'react'; +import { EditorView, lineNumbers as lineNumbersExt } from '@codemirror/view'; +import { autocompletion, closeBrackets } from '@codemirror/autocomplete'; +import { debounce } from 'lodash'; +import { openSearchPanel } from '@codemirror/search'; + +import { + getFileMode, + createNewFileState, + updateFileStates, + createAutocompleteOptions +} from './stateUtils'; +import { useEffectWithComparison } from '../../hooks/custom-hooks'; +import { tidyCodeWithPrettier } from './tidier'; + +// ----- GENERAL TODOS (in order of priority) ----- +// - revisit keymap differences, esp around sublime +// - emmet doesn't trigger if text is copy pasted in +// - need to re-implement emmet auto rename tag +// - color picker should be triggered by metakey cmd k +// - clike addon + +/** This is a custom React hook that manages CodeMirror state. */ +export default function useCodeMirror({ + lineNumbers, + linewrap, + autocloseBracketsQuotes, + setUnsavedChanges, + setCurrentLine, + updateFileContent, + file, + files, + autorefresh, + isPlaying, + clearConsole, + startSketch, + autocompleteHinter, + fontSize, + onUpdateLinting, + referenceBaseUrl +}) { + // The codemirror instance. + const cmView = useRef(); + // The current codemirror files. + const fileStates = useRef(); + + // We have to create a ref for the file ID, or else the debouncer + // will old onto an old version of the fileId and just overrwrite the initial file. + const fileId = useRef(); + fileId.current = file.id; + + // When the file changes, update the file content and save status. + function onChange() { + setUnsavedChanges(true); + updateFileContent(fileId.current, cmView.current.state.doc.toString()); + if (autorefresh && isPlaying) { + clearConsole(); + startSketch(); + } + } + // Call onChange at most once every second. + const debouncedOnChange = debounce(onChange, 1000); + + // This is called when the CM view updates. + function onViewUpdate(updateView) { + const { state } = updateView; + + // TODO - check if need to subtract one + setCurrentLine(state.doc.lineAt(state.selection.main.head).number); + + if (updateView.docChanged) { + debouncedOnChange(); + } + } + + // When the container component enters the DOM, we want this function + // to be called so we can setup the CodeMirror instance with the container. + function setupCodeMirrorOnContainerMounted(container) { + cmView.current = new EditorView({ + parent: container + }); + } + + // When the component unmounts, we want to clean up the CodeMirror instance. + function teardownCodeMirror() { + if (cmView.current) { + cmView.current.destroy(); + cmView.current = null; + } + } + + // When settings change, we pass those changes into CodeMirror. + useEffect(() => { + cmView.current.dom.style['font-size'] = `${fontSize}px`; + }, [fontSize]); + useEffect(() => { + const reconfigureEffect = (fileState) => + fileState.lineWrappingCpt.reconfigure( + linewrap ? EditorView.lineWrapping : [] + ); + updateFileStates({ + fileStates: fileStates.current, + cmView: cmView.current, + file, + reconfigureEffect + }); + }, [linewrap]); + useEffect(() => { + const reconfigureEffect = (fileState) => + fileState.lineNumbersCpt.reconfigure(lineNumbers ? lineNumbersExt() : []); + updateFileStates({ + fileStates: fileStates.current, + cmView: cmView.current, + file, + reconfigureEffect + }); + }, [lineNumbers]); + useEffect(() => { + const reconfigureEffect = (fileState) => + fileState.closeBracketsCpt.reconfigure( + autocloseBracketsQuotes ? closeBrackets() : [] + ); + updateFileStates({ + fileStates: fileStates.current, + cmView: cmView.current, + file, + reconfigureEffect + }); + }, [autocloseBracketsQuotes]); + useEffect(() => { + const reconfigureEffect = (fileState) => + fileState.autocompleteCpt.reconfigure( + autocompleteHinter + ? autocompletion(createAutocompleteOptions(referenceBaseUrl)) + : [] + ); + updateFileStates({ + fileStates: fileStates.current, + cmView: cmView.current, + file, + reconfigureEffect + }); + }, [autocompleteHinter, referenceBaseUrl]); + + // Initializes the files as CodeMirror states. + function initializeDocuments() { + if (!fileStates.current) { + fileStates.current = {}; + } + + files.forEach((currentFile) => { + if ( + currentFile.name !== 'root' && + !(currentFile.id in fileStates.current) + ) { + fileStates.current[currentFile.id] = createNewFileState( + currentFile.name, + currentFile.content, + { + linewrap, + lineNumbers, + autocloseBracketsQuotes, + autocomplete: autocompleteHinter, + onUpdateLinting, + onViewUpdate, + referenceBaseUrl + } + ); + } + }); + } + + // When the files change, reinitialize the documents. + useEffect(initializeDocuments, [files]); + + // When the file changes, make the CodeMirror call to swap out the document. + useEffectWithComparison( + (_, prevProps) => { + // We need to save the previous CodeMirror state so we can restore it + // when we switch back to it. + const previousState = cmView.current.state; + if (Array.isArray(prevProps) && prevProps.length > 0 && previousState) { + const prevId = prevProps[0]; + fileStates.current[prevId].cmState = previousState; + } + + const { cmState } = fileStates.current[file.id]; + cmView.current.setState(cmState); + }, + [file.id] + ); + + const getContent = () => { + const content = cmView.current.state.doc.toString(); + const updatedFile = Object.assign({}, file, { content }); + return updatedFile; + }; + + const showSearch = () => { + openSearchPanel(cmView.current); + }; + + const tidyCode = () => { + const fileMode = getFileMode(file.name); + tidyCodeWithPrettier(cmView.current, fileMode); + }; + + return { + setupCodeMirrorOnContainerMounted, + teardownCodeMirror, + getContent, + tidyCode, + showSearch, + codemirrorView: cmView + }; +} diff --git a/client/modules/IDE/components/Editor/consoleErrorDecoration.js b/client/modules/IDE/components/Editor/consoleErrorDecoration.js new file mode 100644 index 0000000000..ce3890bcca --- /dev/null +++ b/client/modules/IDE/components/Editor/consoleErrorDecoration.js @@ -0,0 +1,52 @@ +import { StateField, StateEffect } from '@codemirror/state'; +import { EditorView, Decoration } from '@codemirror/view'; + +// Effects for communicating with the state field +const ADD_ERROR_DECORATION = StateEffect.define(); +const FILTER_ERROR_DECORATION = StateEffect.define(); + +// An extension for managing error line decorations +// Mostly adapted from the Marked Text demo in https://codemirror.net/docs/migration/ +// You can affect this by calling addErrorDecoration and removeErrorDecorations +export const errorDecorationStateField = StateField.define({ + // Start with an empty set of decorations + create() { + return Decoration.none; + }, + // This is called whenever the editor updates + update(value, transaction) { + // Move the decorations to account for document changes + let newValue = value.map(transaction.changes); + for (let i = 0; i < transaction.effects.length; i++) { + const effect = transaction.effects[i]; + if (effect.is(ADD_ERROR_DECORATION)) + newValue = newValue.update({ add: effect.value, sort: true }); + else if (effect.is(FILTER_ERROR_DECORATION)) + newValue = newValue.update({ filter: effect.value }); + } + return newValue; + }, + // Indicate that this field provides a set of decorations + provide: (f) => EditorView.decorations.from(f) +}); + +const ERROR_DECORATION = Decoration.line({ + class: 'cm-errorLine' // Defined in _editor.scss +}); + +// Add an error decoration to a specific line number +export function addErrorDecoration(view, lineNumber) { + const docLineNumber = view.state.doc.line(lineNumber); + view.dispatch({ + effects: ADD_ERROR_DECORATION.of([ + ERROR_DECORATION.range(docLineNumber.from) + ]) + }); +} + +// Remove all error decorations +export function removeErrorDecorations(view) { + view.dispatch({ + effects: FILTER_ERROR_DECORATION.of(() => false) + }); +} diff --git a/client/modules/IDE/components/Editor/highlightStyle.js b/client/modules/IDE/components/Editor/highlightStyle.js new file mode 100644 index 0000000000..18b3d53c21 --- /dev/null +++ b/client/modules/IDE/components/Editor/highlightStyle.js @@ -0,0 +1,58 @@ +import { HighlightStyle } from '@codemirror/language'; +import { tags } from '@lezer/highlight'; + +export const highlightStyle = HighlightStyle.define([ + { tag: tags.comment, class: 'cm-comment' }, + { tag: tags.lineComment, class: 'cm-comment' }, + { tag: tags.blockComment, class: 'cm-comment' }, + { tag: tags.docComment, class: 'cm-comment' }, + { tag: tags.docString, class: 'cm-comment' }, + { tag: tags.name, class: 'cm-variable' }, + { tag: tags.variableName, class: 'cm-variable' }, + { tag: tags.typeName, class: 'cm-variable' }, + { tag: tags.className, class: 'cm-variable' }, + { tag: tags.string, class: 'cm-string' }, + { tag: tags.character, class: 'cm-string' }, + { tag: tags.attributeName, class: 'cm-string' }, + { tag: tags.regexp, class: 'cm-regexp' }, + { tag: tags.number, class: 'cm-number' }, + { tag: tags.integer, class: 'cm-number' }, + { tag: tags.float, class: 'cm-number' }, + { tag: tags.atom, class: 'cm-atom' }, + { tag: tags.bool, class: 'cm-atom' }, + { tag: tags.null, class: 'cm-atom' }, + { tag: tags.keyword, class: 'cm-keyword' }, + { tag: tags.self, class: 'cm-keyword' }, + { tag: tags.function, class: 'cm-keyword' }, + { tag: tags.operator, class: 'cm-operator' }, + { tag: tags.operatorKeyword, class: 'cm-operator' }, + { tag: tags.controlKeyword, class: 'cm-operator' }, + { tag: tags.derefOperator, class: 'cm-operator' }, + { tag: tags.arithmeticOperator, class: 'cm-operator' }, + { tag: tags.logicOperator, class: 'cm-operator' }, + { tag: tags.bitwiseOperator, class: 'cm-operator' }, + { tag: tags.compareOperator, class: 'cm-operator' }, + { tag: tags.updateOperator, class: 'cm-operator' }, + { tag: tags.typeOperator, class: 'cm-operator' }, + { tag: tags.controlOperator, class: 'cm-operator' }, + { tag: tags.definitionKeyword, class: 'cm-keyword' }, + { tag: tags.tagName, class: 'cm-tag' }, + { tag: tags.heading, class: 'cm-tag' }, + { tag: tags.heading1, class: 'cm-tag' }, + { tag: tags.heading2, class: 'cm-tag' }, + { tag: tags.heading3, class: 'cm-tag' }, + { tag: tags.heading4, class: 'cm-tag' }, + { tag: tags.heading5, class: 'cm-tag' }, + { tag: tags.heading6, class: 'cm-tag' }, + { tag: tags.list, class: 'cm-tag' }, + { tag: tags.quote, class: 'cm-tag' }, + { tag: tags.emphasis, class: 'cm-tag' }, + { tag: tags.strong, class: 'cm-tag' }, + { tag: tags.link, class: 'cm-tag' }, + { tag: tags.propertyName, class: 'cm-property' }, + { tag: tags.attributeName, class: 'cm-attribute' } + + // TODO(connie): Add p5 specific highlighting, like .p5-variable + // There might be a way to do this with Lezer's special tags like + // special(), definition(), const(), local() +]); diff --git a/client/modules/IDE/components/Editor/index.jsx b/client/modules/IDE/components/Editor/index.jsx index 474808da1c..23f1c69900 100644 --- a/client/modules/IDE/components/Editor/index.jsx +++ b/client/modules/IDE/components/Editor/index.jsx @@ -1,55 +1,13 @@ -// TODO: convert to functional component - import PropTypes from 'prop-types'; -import React from 'react'; -import CodeMirror from 'codemirror'; -import Fuse from 'fuse.js'; -import emmet from '@emmetio/codemirror-plugin'; -import prettier from 'prettier/standalone'; -import babelParser from 'prettier/parser-babel'; -import htmlParser from 'prettier/parser-html'; -import cssParser from 'prettier/parser-postcss'; +import React, { useCallback, useEffect, useRef, useState } from 'react'; import { withTranslation } from 'react-i18next'; import StackTrace from 'stacktrace-js'; -import 'codemirror/mode/css/css'; -import 'codemirror/mode/clike/clike'; -import 'codemirror/addon/selection/active-line'; -import 'codemirror/addon/lint/lint'; -import 'codemirror/addon/lint/javascript-lint'; -import 'codemirror/addon/lint/css-lint'; -import 'codemirror/addon/lint/html-lint'; -import 'codemirror/addon/fold/brace-fold'; -import 'codemirror/addon/fold/comment-fold'; -import 'codemirror/addon/fold/foldcode'; -import 'codemirror/addon/fold/foldgutter'; -import 'codemirror/addon/fold/indent-fold'; -import 'codemirror/addon/fold/xml-fold'; -import 'codemirror/addon/comment/comment'; -import 'codemirror/keymap/sublime'; -import 'codemirror/addon/search/searchcursor'; -import 'codemirror/addon/search/matchesonscrollbar'; -import 'codemirror/addon/search/match-highlighter'; -import 'codemirror/addon/search/jump-to-line'; -import 'codemirror/addon/edit/matchbrackets'; -import 'codemirror/addon/edit/closebrackets'; -import 'codemirror/addon/selection/mark-selection'; -import 'codemirror/addon/hint/css-hint'; -import 'codemirror-colorpicker'; -import { JSHINT } from 'jshint'; -import { CSSLint } from 'csslint'; -import { HTMLHint } from 'htmlhint'; import classNames from 'classnames'; import { debounce } from 'lodash'; import { connect } from 'react-redux'; import { bindActionCreators } from 'redux'; import MediaQuery from 'react-responsive'; -import '../../../../utils/htmlmixed'; -import '../../../../utils/p5-javascript'; -import { metaKey } from '../../../../utils/metaKey'; -import '../show-hint'; -import * as hinter from '../../../../utils/p5-hinter'; -import '../../../../utils/codemirror-search'; import beepUrl from '../../../../sounds/audioAlert.mp3'; import RightArrowIcon from '../../../../images/right-arrow.svg'; @@ -73,629 +31,218 @@ import { EditorContainer, EditorHolder } from './MobileEditor'; import { FolderIcon } from '../../../../common/icons'; import { IconButton } from '../../../../common/IconButton'; -import contextAwareHinter from '../../../../utils/contextAwareHinter'; -import showRenameDialog from '../../../../utils/showRenameDialog'; -import handleRename from '../../../../utils/rename-variable'; -import { jumpToDefinition } from '../../../../utils/jump-to-definition'; -import { ensureAriaLiveRegion } from '../../../../utils/ScreenReaderHelper'; -import { isMac } from '../../../../utils/device'; - -emmet(CodeMirror); - -window.JSHINT = JSHINT; -window.CSSLint = CSSLint; -window.HTMLHint = HTMLHint; - -const INDENTATION_AMOUNT = 2; - -class Editor extends React.Component { - constructor(props) { - super(props); - this.state = { - currentLine: 1 - }; - this._cm = null; - this.tidyCode = this.tidyCode.bind(this); - - this.updateLintingMessageAccessibility = debounce((annotations) => { - this.props.clearLintMessage(); - annotations.forEach((x) => { - if (x.from.line > -1) { - this.props.updateLintMessage(x.severity, x.from.line + 1, x.message); - } - }); - if (this.props.lintMessages.length > 0 && this.props.lintWarning) { - this.beep.play(); - } - }, 2000); - this.showFind = this.showFind.bind(this); - this.showReplace = this.showReplace.bind(this); - this.getContent = this.getContent.bind(this); - this.updateFileContent = this.updateFileContent.bind(this); - } - - componentDidMount() { - this.beep = new Audio(beepUrl); - ensureAriaLiveRegion(); - // this.widgets = []; - this._cm = CodeMirror(this.codemirrorContainer, { - theme: `p5-${this.props.theme}`, - lineNumbers: this.props.lineNumbers, - styleActiveLine: true, - inputStyle: 'contenteditable', - lineWrapping: this.props.linewrap, - fixedGutter: false, - foldGutter: true, - foldOptions: { widget: '\u2026' }, - gutters: ['CodeMirror-foldgutter', 'CodeMirror-lint-markers'], - keyMap: 'sublime', - highlightSelectionMatches: true, // highlight current search match - matchBrackets: true, - emmet: { - preview: ['html'], - markTagPairs: true, - autoRenameTags: true - }, - autoCloseBrackets: this.props.autocloseBracketsQuotes, - styleSelectedText: true, - lint: { - onUpdateLinting: (annotations) => { - this.updateLintingMessageAccessibility(annotations); - }, - options: { - asi: true, - eqeqeq: false, - '-W041': false, - esversion: 11 - } - }, - colorpicker: { - type: 'sketch', - mode: 'edit' - } - }); - - this.hinter = new Fuse(hinter.p5Hinter, { - threshold: 0.05, - keys: ['text'] - }); - - delete this._cm.options.lint.options.errors; - - this._cm.getWrapperElement().addEventListener('click', (e) => { - const isCtrlClick = isMac() ? e.metaKey : e.ctrlKey; +import useCodeMirror from './codemirror'; - if (isCtrlClick) { - const pos = this._cm.coordsChar({ left: e.clientX, top: e.clientY }); - jumpToDefinition.call(this, pos); - } - }); - - const renameKey = isMac() ? 'Ctrl-F2' : 'F2'; - - const replaceCommand = - metaKey === 'Ctrl' ? `${metaKey}-H` : `${metaKey}-Option-F`; - this._cm.setOption('extraKeys', { - Tab: (cm) => { - if (!cm.execCommand('emmetExpandAbbreviation')) return; - // might need to specify and indent more? - const selection = cm.doc.getSelection(); - if (selection.length > 0) { - cm.execCommand('indentMore'); - } else { - cm.replaceSelection(' '.repeat(INDENTATION_AMOUNT)); - } - }, - Enter: 'emmetInsertLineBreak', - Esc: 'emmetResetAbbreviation', - [`Shift-${metaKey}-E`]: (cm) => { - cm.getInputField().blur(); - }, - [renameKey]: (cm) => this.renameVariable(cm), - [`Shift-Tab`]: false, - [`${metaKey}-Enter`]: () => null, - [`Shift-${metaKey}-Enter`]: () => null, - [`${metaKey}-F`]: 'findPersistent', - [`Shift-${metaKey}-F`]: this.tidyCode, - [`${metaKey}-G`]: 'findPersistentNext', - [`Shift-${metaKey}-G`]: 'findPersistentPrev', - [replaceCommand]: 'replace', - // Cassie Tarakajian: If you don't set a default color, then when you - // choose a color, it deletes characters inline. This is a - // hack to prevent that. - [`${metaKey}-K`]: (cm, event) => - cm.state.colorpicker.popup_color_picker({ length: 0 }), - [`${metaKey}-.`]: 'toggleComment' // Note: most adblockers use the shortcut ctrl+. - }); +import { + addErrorDecoration, + removeErrorDecorations +} from './consoleErrorDecoration'; - this.initializeDocuments(this.props.files); - this._cm.swapDoc(this._docs[this.props.file.id]); +// temporary until p5.js 2.0 becomes default +// checks if sketch is using p5.js 2.0 to pass correct base url for autocomplete hinter reference +export function getReferenceBaseUrl(htmlFile) { + const html = htmlFile?.content || ''; - this._cm.on( - 'change', - debounce(() => { - this.props.setUnsavedChanges(true); - this.props.hideRuntimeErrorWarning(); - this.props.updateFileContent(this.props.file.id, this._cm.getValue()); - if (this.props.autorefresh && this.props.isPlaying) { - this.props.clearConsole(); - this.props.startSketch(); - } - }, 1000) - ); + const isV2 = + /https:\/\/beta\.p5js\.org\b/i.test(html) || /\bp5(@|-)2\./i.test(html); - if (this._cm) { - this._cm.on('keyup', this.handleKeyUp); - } + return isV2 ? 'https://beta.p5js.org' : 'https://p5js.org'; +} - this._cm.on('keydown', (_cm, e) => { - // Skip hinting if the user is pasting (Ctrl/Cmd+V) or using modifier keys (Ctrl/Alt) - if ( - ((e.ctrlKey || e.metaKey) && e.key === 'v') || - e.ctrlKey || - e.altKey - ) { - return; - } - const mode = this._cm.getOption('mode'); - if (/^[a-z]$/i.test(e.key) && (mode === 'css' || mode === 'javascript')) { - this.showHint(_cm); +function Editor({ + provideController, + files, + file, + linewrap, + lineNumbers, + closeProjectOptions, + setSelectedFile, + setUnsavedChanges, + lintMessages, + lintWarning, + clearLintMessage, + updateLintMessage, + updateFileContent, + autorefresh, + isPlaying, + clearConsole, + startSketch, + autocompleteHinter, + autocloseBracketsQuotes, + fontSize, + consoleEvents, + expandConsole, + isExpanded, + htmlFile, + t, + collapseSidebar, + expandSidebar +}) { + const [currentLine, setCurrentLine] = useState(1); + const beep = useRef(); + + const updateLintingMessageAccessibility = debounce((annotations) => { + clearLintMessage(); + annotations.forEach((x) => { + if (x.from.line > -1) { + updateLintMessage(x.severity, x.from.line + 1, x.message); } }); - - this._cm.getWrapperElement().style[ - 'font-size' - ] = `${this.props.fontSize}px`; - - this.props.provideController({ - tidyCode: this.tidyCode, - showFind: this.showFind, - showReplace: this.showReplace, - getContent: this.getContent, - updateFileContent: this.updateFileContent - }); - } - - componentWillUpdate(nextProps) { - // check if files have changed - if (this.props.files[0].id !== nextProps.files[0].id) { - // then need to make CodeMirror documents - this.initializeDocuments(nextProps.files); + if (lintMessages.length > 0 && lintWarning) { + beep.play(); } - if (this.props.files.length !== nextProps.files.length) { - this.initializeDocuments(nextProps.files); - } - } - - componentDidUpdate(prevProps) { - if (this.props.file.id !== prevProps.file.id) { - const fileMode = this.getFileMode(this.props.file.name); - if (fileMode === 'javascript') { - // Define the new Emmet configuration based on the file mode - const emmetConfig = { - preview: ['html'], - markTagPairs: false, - autoRenameTags: true - }; - this._cm.setOption('emmet', emmetConfig); - } - const oldDoc = this._cm.swapDoc(this._docs[this.props.file.id]); - this._docs[prevProps.file.id] = oldDoc; - this._cm.focus(); - - if (!prevProps.unsavedChanges) { - setTimeout(() => this.props.setUnsavedChanges(false), 400); - } - } else if (this.getContent().content !== this.props.file.content) { - // TODO: make this not break regular edits! - // this._cm.setValue(this.props.file.content); - } - if (this.props.fontSize !== prevProps.fontSize) { - this._cm.getWrapperElement().style[ - 'font-size' - ] = `${this.props.fontSize}px`; - } - if (this.props.linewrap !== prevProps.linewrap) { - this._cm.setOption('lineWrapping', this.props.linewrap); - } - if (this.props.theme !== prevProps.theme) { - this._cm.setOption('theme', `p5-${this.props.theme}`); - } - if (this.props.lineNumbers !== prevProps.lineNumbers) { - this._cm.setOption('lineNumbers', this.props.lineNumbers); - } - if ( - this.props.autocloseBracketsQuotes !== prevProps.autocloseBracketsQuotes - ) { - this._cm.setOption( - 'autoCloseBrackets', - this.props.autocloseBracketsQuotes - ); - } - if (this.props.autocompleteHinter !== prevProps.autocompleteHinter) { - if (!this.props.autocompleteHinter) { - // close the hinter window once the preference is turned off - CodeMirror.showHint(this._cm, () => {}, {}); - } - } - - if (this.props.runtimeErrorWarningVisible) { - if (this.props.consoleEvents.length !== prevProps.consoleEvents.length) { - this.props.consoleEvents.forEach((consoleEvent) => { - if (consoleEvent.method === 'error') { - // It doesn't work if you create a new Error, but this works - // LOL - const errorObj = { stack: consoleEvent.data[0].toString() }; - StackTrace.fromError(errorObj).then((stackLines) => { - this.props.expandConsole(); - const line = stackLines.find( - (l) => l.fileName && l.fileName.startsWith('/') - ); - if (!line) return; - const fileNameArray = line.fileName.split('/'); - const fileName = fileNameArray.slice(-1)[0]; - const filePath = fileNameArray.slice(0, -1).join('/'); - const fileWithError = this.props.files.find( - (f) => f.name === fileName && f.filePath === filePath - ); - this.props.setSelectedFile(fileWithError.id); - this._cm.addLineClass( - line.lineNumber - 1, - 'background', - 'line-runtime-error' - ); - }); - } - }); - } else { - for (let i = 0; i < this._cm.lineCount(); i += 1) { - this._cm.removeLineClass(i, 'background', 'line-runtime-error'); - } - } - } - - if (this.props.file.id !== prevProps.file.id) { - for (let i = 0; i < this._cm.lineCount(); i += 1) { - this._cm.removeLineClass(i, 'background', 'line-runtime-error'); - } - } - - this.props.provideController({ - tidyCode: this.tidyCode, - showFind: this.showFind, - showReplace: this.showReplace, - getContent: this.getContent, - updateFileContent: this.updateFileContent + }, 2000); + + // The useCodeMirror hook manages CodeMirror state and returns + // a reference to the actual CM instance. + const { + setupCodeMirrorOnContainerMounted, + teardownCodeMirror, + codemirrorView, + getContent, + tidyCode, + showSearch + } = useCodeMirror({ + lineNumbers, + linewrap, + autocloseBracketsQuotes, + setUnsavedChanges, + updateFileContent, + file, + files, + autorefresh, + isPlaying, + clearConsole, + startSketch, + autocompleteHinter, + fontSize, + updateLintingMessageAccessibility, + setCurrentLine, + referenceBaseUrl: getReferenceBaseUrl(htmlFile) + }); + + // Lets the parent component access file content-specific functionality... + useEffect(() => { + provideController({ + tidyCode, + getContent, + showSearch }); - } - - componentWillUnmount() { - if (this._cm) { - this._cm.off('keyup', this.handleKeyUp); - } - this.props.provideController(null); - } + }, [getContent]); - getFileMode(fileName) { - let mode; - if (fileName.match(/.+\.js$/i)) { - mode = 'javascript'; - } else if (fileName.match(/.+\.css$/i)) { - mode = 'css'; - } else if (fileName.match(/.+\.(html|xml)$/i)) { - mode = 'htmlmixed'; - } else if (fileName.match(/.+\.json$/i)) { - mode = 'application/json'; - } else if (fileName.match(/.+\.(frag|glsl)$/i)) { - mode = 'x-shader/x-fragment'; - } else if (fileName.match(/.+\.(vert|stl|mtl)$/i)) { - mode = 'x-shader/x-vertex'; - } else { - mode = 'text/plain'; - } - return mode; - } + // When the CM container div mounts, we set up CodeMirror. + const onContainerMounted = useCallback(setupCodeMirrorOnContainerMounted, []); - getContent() { - const content = this._cm.getValue(); - const updatedFile = Object.assign({}, this.props.file, { content }); - return updatedFile; - } + // This is acting as a "componentDidMount" call where it runs once + // at the start and never again. It also provides a cleanup function. + useEffect(() => { + beep.current = new Audio(beepUrl); - updateFileContent(id, src) { - const file = this._docs[id]; - if (file) { - this._docs[id] = CodeMirror.Doc(src, this._docs[id].modeOption); - if (id === this.props.file.id) { - this._cm.swapDoc(this._docs[id]); - } - } - } - - handleKeyUp = () => { - const lineNumber = parseInt(this._cm.getCursor().line + 1, 10); - this.setState({ currentLine: lineNumber }); - }; - - showFind() { - this._cm.execCommand('findPersistent'); - } - - // temporary until p5.js 2.0 becomes default - // checks if sketch is using p5.js 2.0 to pass correct base url for autocomplete hinter reference - getReferenceBaseUrl = () => { - const html = this.props.htmlFile?.content || ''; - - const isV2 = - /https:\/\/beta\.p5js\.org\b/i.test(html) || /\bp5(@|-)2\./i.test(html); - - return isV2 ? 'https://beta.p5js.org' : 'https://p5js.org'; - }; - - showHint(_cm) { - if (!_cm) return; - - if (!this.props.autocompleteHinter) { - CodeMirror.showHint(_cm, () => {}, {}); - return; - } - - let focusedLinkElement = null; - - const setFocusedLinkElement = (set) => { - if (set && !focusedLinkElement) { - const activeItemLink = document.querySelector( - `.CodeMirror-hint-active a` - ); - if (activeItemLink) { - focusedLinkElement = activeItemLink; - focusedLinkElement.classList.add('focused-hint-link'); - focusedLinkElement.parentElement.parentElement.classList.add( - 'unfocused' - ); - } - } + return () => { + provideController(null); + teardownCodeMirror(); }; - - const removeFocusedLinkElement = () => { - if (focusedLinkElement) { - focusedLinkElement.classList.remove('focused-hint-link'); - focusedLinkElement.parentElement.parentElement.classList.remove( - 'unfocused' + }, []); + + // Updates the runtime error console. + useEffect(() => { + const consoleErrors = consoleEvents.filter((e) => e.method === 'error'); + + if (consoleErrors.length > 0) { + const firstError = consoleErrors[0]; + const errorObj = { stack: firstError.data[0].toString() }; + StackTrace.fromError(errorObj).then((stackLines) => { + expandConsole(); + const line = stackLines.find( + (l) => l.fileName && l.fileName.startsWith('/') ); - focusedLinkElement = null; - return true; - } - return false; - }; - - const hintOptions = { - _fontSize: this.props.fontSize, - referenceBaseUrl: this.getReferenceBaseUrl(), - completeSingle: false, - extraKeys: { - 'Shift-Right': (cm, e) => { - const activeItemLink = document.querySelector( - `.CodeMirror-hint-active a` - ); - if (activeItemLink) activeItemLink.click(); - }, - Right: (cm, e) => setFocusedLinkElement(true), - Left: (cm, e) => removeFocusedLinkElement(), - Up: (cm, e) => { - const onLink = removeFocusedLinkElement(); - e.moveFocus(-1); - setFocusedLinkElement(onLink); - }, - Down: (cm, e) => { - const onLink = removeFocusedLinkElement(); - e.moveFocus(1); - setFocusedLinkElement(onLink); - }, - Enter: (cm, e) => { - if (focusedLinkElement) focusedLinkElement.click(); - else e.pick(); - } - }, - closeOnUnfocus: false - }; - - const triggerHints = () => { - if (_cm.options.mode === 'javascript') { - CodeMirror.showHint( - _cm, - () => { - const c = _cm.getCursor(); - const token = _cm.getTokenAt(c); - const hints = contextAwareHinter(_cm, { hinter: this.hinter }); - return { - list: hints, - from: CodeMirror.Pos(c.line, token.start), - to: CodeMirror.Pos(c.line, c.ch) - }; - }, - hintOptions + if (!line) return; + const fileNameArray = line.fileName.split('/'); + const fileName = fileNameArray.slice(-1)[0]; + const filePath = fileNameArray.slice(0, -1).join('/'); + const fileWithError = files.find( + (f) => f.name === fileName && f.filePath === filePath ); - } else if (_cm.options.mode === 'css') { - CodeMirror.showHint(_cm, CodeMirror.hint.css, hintOptions); - } - }; - - setTimeout(triggerHints, 0); - } - - showReplace() { - this._cm.execCommand('replace'); - } - - prettierFormatWithCursor(parser, plugins) { - try { - const { formatted, cursorOffset } = prettier.formatWithCursor( - this._cm.doc.getValue(), - { - cursorOffset: this._cm.doc.indexFromPos(this._cm.doc.getCursor()), - parser, - plugins - } - ); - const { left, top } = this._cm.getScrollInfo(); - this._cm.doc.setValue(formatted); - this._cm.focus(); - this._cm.doc.setCursor(this._cm.doc.posFromIndex(cursorOffset)); - this._cm.scrollTo(left, top); - } catch (error) { - console.error(error); - } - } - - tidyCode() { - const mode = this._cm.getOption('mode'); - if (mode === 'javascript') { - this.prettierFormatWithCursor('babel', [babelParser]); - } else if (mode === 'css') { - this.prettierFormatWithCursor('css', [cssParser]); - } else if (mode === 'htmlmixed') { - this.prettierFormatWithCursor('html', [htmlParser]); - } - } - - renameVariable(cm) { - const cursorCoords = cm.cursorCoords(true, 'page'); - const selection = cm.getSelection(); - const pos = cm.getCursor(); // or selection start - const token = cm.getTokenAt(pos); - const tokenType = token.type; - if (!selection) { - return; + setSelectedFile(fileWithError.id); + addErrorDecoration(codemirrorView.current, line.lineNumber); + }); + } else { + removeErrorDecorations(codemirrorView.current); } - - const sel = cm.listSelections()[0]; - const fromPos = - CodeMirror.cmpPos(sel.anchor, sel.head) <= 0 ? sel.anchor : sel.head; - - showRenameDialog( - cm, - fromPos, - tokenType, - cursorCoords, - selection, - (newName) => { - if (newName && newName.trim() !== '' && newName !== selection) { - handleRename(fromPos, selection, newName, cm); - } - } - ); - } - - initializeDocuments(files) { - this._docs = {}; - files.forEach((file) => { - if (file.name !== 'root') { - this._docs[file.id] = CodeMirror.Doc( - file.content, - this.getFileMode(file.name) - ); // eslint-disable-line - } - }); - } - - render() { - const editorSectionClass = classNames({ - editor: true, - 'sidebar--contracted': !this.props.isExpanded - }); - - const editorHolderClass = classNames({ - 'editor-holder': true, - 'editor-holder--hidden': - this.props.file.fileType === 'folder' || this.props.file.url - }); - - const { currentLine } = this.state; - - return ( - - {(matches) => - matches ? ( -
-
- - -
- - {this.props.file.name} - - - -
-
-
{ - this.codemirrorContainer = element; + }, [consoleEvents]); + + const editorSectionClass = classNames({ + editor: true, + 'sidebar--contracted': !isExpanded + }); + + const editorHolderClass = classNames({ + 'editor-holder': true, + 'editor-holder--hidden': file.fileType === 'folder' || file.url + }); + + return ( + + {(matches) => + matches ? ( +
+
+ + +
+ + {file.name} + + + +
+
+
+ {file.url ? : null} + +
+ ) : ( + +
+ + + {file.name} + + +
+
+ + {file.url ? ( + ) : null}
- ) : ( - -
- - - {this.props.file.name} - - -
-
- { - this.codemirrorContainer = element; - }} - /> - {this.props.file.url ? ( - - ) : null} - -
-
- ) - } -
- ); - } + + ) + } + + ); } Editor.propTypes = { @@ -734,8 +281,6 @@ Editor.propTypes = { startSketch: PropTypes.func.isRequired, autorefresh: PropTypes.bool.isRequired, isPlaying: PropTypes.bool.isRequired, - theme: PropTypes.string.isRequired, - unsavedChanges: PropTypes.bool.isRequired, files: PropTypes.arrayOf( PropTypes.shape({ id: PropTypes.string.isRequired, @@ -743,16 +288,14 @@ Editor.propTypes = { content: PropTypes.string.isRequired }) ).isRequired, + isExpanded: PropTypes.bool.isRequired, htmlFile: PropTypes.shape({ content: PropTypes.string }), - isExpanded: PropTypes.bool.isRequired, collapseSidebar: PropTypes.func.isRequired, closeProjectOptions: PropTypes.func.isRequired, expandSidebar: PropTypes.func.isRequired, clearConsole: PropTypes.func.isRequired, - hideRuntimeErrorWarning: PropTypes.func.isRequired, - runtimeErrorWarningVisible: PropTypes.bool.isRequired, provideController: PropTypes.func.isRequired, t: PropTypes.func.isRequired, setSelectedFile: PropTypes.func.isRequired, diff --git a/client/modules/IDE/components/Editor/p5CompletionPreview.js b/client/modules/IDE/components/Editor/p5CompletionPreview.js new file mode 100644 index 0000000000..5538744db7 --- /dev/null +++ b/client/modules/IDE/components/Editor/p5CompletionPreview.js @@ -0,0 +1,108 @@ +import { StateField, RangeSetBuilder } from '@codemirror/state'; +import { Decoration, EditorView, WidgetType } from '@codemirror/view'; +import { selectedCompletion, completionStatus } from '@codemirror/autocomplete'; + +class GhostTextWidget extends WidgetType { + constructor(text) { + super(); + this.text = text; + } + + eq(other) { + return other.text === this.text; + } + + toDOM() { + const span = document.createElement('span'); + span.className = 'cm-ghostCompletion'; + span.textContent = this.text; + return span; + } + + ignoreEvent() { + return true; + } +} + +function getCurrentWord(state) { + const { from, to, empty } = state.selection.main; + if (!empty) return null; + + const line = state.doc.lineAt(from); + const before = line.text.slice(0, from - line.from); + const match = before.match(/\w+$/); + + if (!match) return null; + + const word = match[0]; + return { + text: word, + from: from - word.length, + to + }; +} + +function buildGhostText(state) { + // only show ghost text if autocomplete is on, + // user is typing, and if preview matches typed text + + if (completionStatus(state) !== 'active') return null; + + const selected = selectedCompletion(state); + if (!selected) return null; + + const word = getCurrentWord(state); + if (!word) return null; + + const preview = selected.preview || selected.label; + if (!preview) return null; + + if (!preview.toLowerCase().startsWith(word.text.toLowerCase())) return null; + + const remainder = preview.slice(word.text.length); + if (!remainder) return null; + + return { + pos: word.to, + text: remainder + }; +} + +const ghostTextField = StateField.define({ + create(state) { + return Decoration.none; + }, + + update(deco, tr) { + const decorationBuilder = new RangeSetBuilder(); + const ghost = buildGhostText(tr.state); + + if (ghost) { + decorationBuilder.add( + ghost.pos, + ghost.pos, + Decoration.widget({ + widget: new GhostTextWidget(ghost.text), + side: 1 + }) + ); + } + + return decorationBuilder.finish(); + }, + + provide: (field) => EditorView.decorations.from(field) +}); + +export const p5CompletionPreviewTheme = EditorView.theme({ + '.cm-ghostCompletion': { + opacity: '0.55', + fontStyle: 'italic', + pointerEvents: 'none', + whiteSpace: 'pre' + } +}); + +export function p5CompletionPreview() { + return [ghostTextField, p5CompletionPreviewTheme]; +} diff --git a/client/modules/IDE/components/Editor/p5JavaScript.js b/client/modules/IDE/components/Editor/p5JavaScript.js new file mode 100644 index 0000000000..f729899dd9 --- /dev/null +++ b/client/modules/IDE/components/Editor/p5JavaScript.js @@ -0,0 +1,28 @@ +import { LanguageSupport } from '@codemirror/language'; +import { javascript } from '@codemirror/lang-javascript'; +import { p5Hinter } from '../../../../utils/p5-hinter'; +import { p5CompletionPreview } from './p5CompletionPreview'; +import contextAwareHinter from '../../../../utils/contextAwareHinter'; + +function addCompletions(context) { + const word = context.matchBefore(/\w*/); + + if (!word && !context.explicit) { + return null; + } + + return contextAwareHinter(context, { + hints: p5Hinter + }); +} + +export default function p5JavaScript() { + const jsLang = javascript(); + return new LanguageSupport(jsLang.language, [ + jsLang.extension, + jsLang.language.data.of({ + autocomplete: addCompletions + }), + p5CompletionPreview() + ]); +} diff --git a/client/modules/IDE/components/Editor/stateUtils.js b/client/modules/IDE/components/Editor/stateUtils.js new file mode 100644 index 0000000000..fc20224eff --- /dev/null +++ b/client/modules/IDE/components/Editor/stateUtils.js @@ -0,0 +1,525 @@ +import { EditorState, Compartment } from '@codemirror/state'; +import { + EditorView, + lineNumbers as lineNumbersExt, + highlightActiveLine, + highlightActiveLineGutter, + gutters, + keymap, + highlightSpecialChars, + drawSelection, + dropCursor, + rectangularSelection, + crosshairCursor +} from '@codemirror/view'; +import { + foldGutter, + foldKeymap, + bracketMatching, + indentOnInput, + syntaxHighlighting +} from '@codemirror/language'; +import { + autocompletion, + closeBrackets, + closeBracketsKeymap, + completionStatus, + selectedCompletionIndex +} from '@codemirror/autocomplete'; +import { + highlightSelectionMatches, + search, + searchKeymap +} from '@codemirror/search'; +import { + defaultKeymap, + history, + historyKeymap, + insertTab, + indentLess +} from '@codemirror/commands'; +import { lintGutter } from '@codemirror/lint'; +import { + expandAbbreviation, + abbreviationTracker +} from '@emmetio/codemirror6-plugin'; + +import { css } from '@codemirror/lang-css'; +import { html } from '@codemirror/lang-html'; +import { json, jsonParseLinter } from '@codemirror/lang-json'; +import { xml } from '@codemirror/lang-xml'; +import { linter } from '@codemirror/lint'; +import { HTMLHint } from 'htmlhint'; +import { CSSLint } from 'csslint'; +import { emmetConfig } from '@emmetio/codemirror6-plugin'; +import { color as colorPicker } from '@connieye/codemirror-color-picker'; +import { esLint } from '@codemirror/lang-javascript'; +import { Linter as ESLinter } from 'eslint-linter-browserify'; +import { tidyCodeWithPrettier } from './tidier'; +import p5JavaScript from './p5JavaScript'; +import { highlightStyle } from './highlightStyle'; +import { errorDecorationStateField } from './consoleErrorDecoration'; + +// ----- TODOS ----- +// - shader syntax highlighting + +/** Detects what mode the file is based on the name. */ +export function getFileMode(fileName) { + let mode; + if (fileName.match(/.+\.js$/i)) { + mode = 'javascript'; + } else if (fileName.match(/.+\.css$/i)) { + mode = 'css'; + } else if (fileName.match(/.+\.(html)$/i)) { + mode = 'html'; + } else if (fileName.match(/.+\.(xml)$/i)) { + mode = 'xml'; + } else if (fileName.match(/.+\.json$/i)) { + mode = 'application/json'; + } else if (fileName.match(/.+\.(frag|glsl)$/i)) { + mode = 'x-shader/x-fragment'; + } else if (fileName.match(/.+\.(vert|stl|mtl)$/i)) { + mode = 'x-shader/x-vertex'; + } else { + mode = 'text/plain'; + } + return mode; +} + +function getFileLanguage(fileName) { + const fileMode = getFileMode(fileName); + + switch (fileMode) { + case 'javascript': + return p5JavaScript; + case 'css': + return css; + case 'html': + return html; + case 'xml': + return xml; + case 'application/json': + return json; + default: + return null; + } +} + +function makeCssLinter(callback) { + return (view) => { + const documentContent = view.state.doc.toString(); + const { messages } = CSSLint.verify(documentContent, {}); + const diagnostics = []; + messages.forEach((message) => { + if (!message) return; + + const { + line: messageLine, + col: messageCharacter, + type: messageType, + message: messageText + } = message; + const cmLine = view.state.doc.line(messageLine); + + // TODO: Can we to do the to/from smarter? + diagnostics.push({ + from: cmLine.from + messageCharacter - 1, + to: cmLine.from + messageCharacter, + severity: messageType, + message: messageText + }); + }); + + if (callback) callback(diagnostics); + + return diagnostics; + }; +} + +// https://github.com/codemirror/codemirror5/blob/master/addon/lint/html-lint.js +const HTMLHINT_OPTIONS = { + 'tagname-lowercase': true, + 'attr-lowercase': true, + 'attr-value-double-quotes': true, + 'doctype-first': false, + 'tag-pair': true, + 'spec-char-escape': true, + 'id-unique': true, + 'src-not-empty': true, + 'attr-no-duplication': true +}; + +function makeHtmlLinter(callback) { + return (view) => { + const documentContent = view.state.doc.toString(); + + const messages = HTMLHint.verify(documentContent, HTMLHINT_OPTIONS) || []; + + const diagnostics = []; + messages.forEach((message) => { + if (!message) return; + + const { + line: messageLine, + col: messageCharacter, + type: messageType, + message: messageText + } = message; + const cmLine = view.state.doc.line(messageLine); + + // TODO: Can we to do the to/from smarter? + diagnostics.push({ + from: cmLine.from + messageCharacter - 1, + to: cmLine.from + messageCharacter, + severity: messageType, + message: messageText + }); + }); + + if (callback) callback(diagnostics); + + return diagnostics; + }; +} + +const ESLINT_CONFIG = { + languageOptions: { + ecmaVersion: 2021 + }, + rules: { + semi: 'off', + eqeqeq: 'off' + } +}; + +const eslint = new ESLinter(); + +function makeJsonLinter(callback) { + const baseJsonLinter = jsonParseLinter(); + return (view) => { + const diagnostics = baseJsonLinter(view); + if (callback) callback(diagnostics); + return diagnostics; + }; +} + +function getFileLinter(fileName, callback) { + const fileMode = getFileMode(fileName); + + switch (fileMode) { + case 'javascript': + return linter(esLint(eslint, ESLINT_CONFIG)); + case 'html': + return linter(makeHtmlLinter(callback)); + case 'css': + return linter(makeCssLinter(callback)); + case 'application/json': + return linter(makeJsonLinter(callback)); + default: + return null; + } +} + +function getFileEmmetConfig(fileName) { + const fileMode = getFileMode(fileName); + + switch (fileMode) { + case 'html': + return emmetConfig.of({ syntax: 'html' }); + case 'css': + return emmetConfig.of({ syntax: 'css' }); + default: + return null; + } +} + +function focusOnReferenceArrow(view) { + if (completionStatus(view.state) !== 'active') return false; + + const selectedIndex = selectedCompletionIndex(view.state); + if (selectedIndex == null || selectedIndex < 0) return false; + + const tooltip = view.dom.querySelector('.cm-tooltip-autocomplete'); + if (!tooltip) return false; + + const options = tooltip.querySelectorAll('li.CodeMirror-hint'); + const selectedOption = options[selectedIndex]; + if (!selectedOption) return false; + + const link = selectedOption.querySelector('.cm-completionRefLink'); + if (!link) return false; + + link.focus(); + link.classList.add('focused-hint-link'); + + const cleanup = () => { + link.classList.remove('focused-hint-link'); + link.removeEventListener('blur', cleanup); + }; + link.addEventListener('blur', cleanup); + + return true; +} + +// Extra custom keymaps. +// TODO: We need to add sublime mappings + other missing extra mappings here. +const extraKeymaps = [ + { key: 'ArrowRight', run: focusOnReferenceArrow }, + { key: 'Tab', run: insertTab, shift: indentLess } +]; +const emmetKeymaps = [{ key: 'Tab', run: expandAbbreviation }]; + +/** Returns completion options configured for autocomplete. */ +export const createAutocompleteOptions = (referenceBaseUrl) => ({ + tooltipClass: () => 'CodeMirror-hints', + closeOnBlur: false, + icons: false, + + // handle css classes + optionClass(completion) { + let className = 'CodeMirror-hint'; + + if (completion.type) { + className += ` hint-type-${completion.type}`; + } + + if (completion.p5DocPath) { + className += ' has-doc-link'; + } + + return className; + }, + + addToOptions: [ + { + position: 60, + render(completion) { + const kind = document.createElement('span'); + kind.className = 'cm-completionKind'; + kind.textContent = completion.kindLabel || completion.type || ''; + return kind; + } + }, + { + position: 80, + render(completion, state, view) { + if (!completion.p5DocPath) return null; + + // TODO: add in reference url version switching + const link = document.createElement('a'); + link.className = 'cm-completionRefLink'; + link.href = `${referenceBaseUrl}/reference/p5/${completion.p5DocPath}`; + link.target = '_blank'; + link.rel = 'noopener noreferrer'; + link.tabIndex = -1; + link.setAttribute('aria-label', `Open ${completion.label} reference`); + + link.innerHTML = ` + open ${completion.label} reference + + `; + + link.addEventListener('mousedown', (event) => { + event.preventDefault(); + event.stopPropagation(); + }); + + link.addEventListener('click', (event) => { + event.stopPropagation(); + }); + + link.addEventListener('keydown', (event) => { + if (event.key === 'ArrowLeft' || event.key === 'Escape') { + event.preventDefault(); + event.stopPropagation(); + link.classList.remove('focused-hint-link'); + view.focus(); + } + }); + + return link; + } + }, + { + position: 100, + render(completion) { + if (!completion.blacklisted) return null; + + const warning = document.createElement('div'); + warning.className = 'cm-completionWarning'; + + const icon = document.createElement('span'); + icon.className = 'cm-completionWarningIcon'; + icon.setAttribute('aria-hidden', 'true'); + icon.textContent = '⚠️'; + + const text = document.createElement('span'); + text.className = 'cm-completionWarningText'; + text.textContent = 'use with caution in this context'; + + warning.appendChild(icon); + warning.appendChild(text); + + return warning; + } + } + ] +}); + +/** + * Creates a new CodeMirror editor state with configurations, + * extensions, and keymaps tailored to the file type and settings. + * + * Returns a "file state" object containing the CodeMirror state and compartments. + */ +export function createNewFileState(filename, document, settings) { + const { + linewrap, + lineNumbers, + autocomplete, + autocloseBracketsQuotes, + onUpdateLinting, + onViewUpdate, + referenceBaseUrl + } = settings; + const lineNumbersCpt = new Compartment(); + const lineWrappingCpt = new Compartment(); + const closeBracketsCpt = new Compartment(); + const autocompleteCpt = new Compartment(); + + // Depending on the file mode, we have a different tidier function. + const mode = getFileMode(filename); + extraKeymaps.push({ + key: `Shift-Mod-F`, + run: (cmView) => tidyCodeWithPrettier(cmView, mode) + }); + + const keymaps = [ + extraKeymaps, + closeBracketsKeymap, + defaultKeymap, + historyKeymap, + foldKeymap, + searchKeymap + ]; + + // https://github.com/codemirror/basic-setup/blob/main/src/codemirror.ts + const extensions = [ + // The first few extensions can be toggled on or off. + lineNumbersCpt.of(lineNumbers ? lineNumbersExt() : []), + lineWrappingCpt.of(linewrap ? EditorView.lineWrapping : []), + closeBracketsCpt.of(autocloseBracketsQuotes ? closeBrackets() : []), + autocompleteCpt.of( + autocomplete + ? autocompletion(createAutocompleteOptions(referenceBaseUrl)) + : [] + ), + + // Everything below here should always be on. + history(), + search(), + // Highlight extensions + highlightActiveLine(), + highlightActiveLineGutter(), + highlightSpecialChars(), + highlightSelectionMatches(), + syntaxHighlighting(highlightStyle), + // Selection extensions + drawSelection(), + rectangularSelection(), + dropCursor(), + crosshairCursor(), + EditorState.allowMultipleSelections.of(true), + // Gutter extensions + gutters({ fixed: false }), + foldGutter(), + // Misc extensions + indentOnInput(), + bracketMatching(), + errorDecorationStateField, + + // Setup the event listeners on the CodeMirror instance. + EditorView.updateListener.of(onViewUpdate) + ]; + + // Only enable the color picker for Javascript and CSS, which + // have both been tested. + const fileMode = getFileMode(filename); + if (fileMode === 'javascript' || fileMode === 'css') { + extensions.push(colorPicker); + } + + const fileLanguage = getFileLanguage(filename); + const fileLinter = getFileLinter(filename, onUpdateLinting); + const fileEmmetConfig = getFileEmmetConfig(filename); + + if (fileLanguage) { + extensions.push(fileLanguage()); + } + if (fileLinter) { + extensions.push(fileLinter); + extensions.push(lintGutter()); + } + + // If it's HTML or CSS, we add some emmet-specific configs. + if (fileEmmetConfig) { + extensions.push(fileEmmetConfig); + extensions.push(abbreviationTracker()); + keymaps.push(emmetKeymaps); + } + + // Now add the keymaps... + extensions.push(keymap.of(keymaps.flat())); + + // Create the state with document content if we have it. + const stateOptions = { + extensions + }; + if (document) { + stateOptions.doc = document; + } + + const cmState = EditorState.create(stateOptions); + return { + cmState, + lineNumbersCpt, + lineWrappingCpt, + closeBracketsCpt, + autocompleteCpt + }; +} + +/** + * Given a reconfigure effect, this function will update all + * of the file states. + * + * We need to do this whenever the settings like line numbers + * change, so it will get called in the useEffect hooks. + */ +export function updateFileStates({ + fileStates, + cmView, + file: currentFile, + reconfigureEffect +}) { + if (!fileStates) return; + + Object.entries(fileStates).forEach(([fileId, fileState]) => { + // Either grab the current state from the view or saved in the fileStates. + let { cmState } = fileState; + if (fileId === currentFile.id) { + cmState = cmView.state; + } + + // Apply the new effects and grab the new state. + const { state: newCmState } = cmState.update({ + effects: reconfigureEffect(fileState) + }); + + // Save the new states and update the view for the currently open file. + fileStates[fileId].cmState = newCmState; + if (fileId === currentFile.id) { + cmView.setState(newCmState); + } + }); +} diff --git a/client/modules/IDE/components/Editor/tidier.test.ts b/client/modules/IDE/components/Editor/tidier.test.ts new file mode 100644 index 0000000000..babb27c8b2 --- /dev/null +++ b/client/modules/IDE/components/Editor/tidier.test.ts @@ -0,0 +1,67 @@ +import { EditorState } from '@codemirror/state'; +import { EditorView } from '@codemirror/view'; +import { tidyCodeWithPrettier } from './tidier'; + +describe('tidyCodeWithPrettier', () => { + function createEditor(content: string) { + const state = EditorState.create({ doc: content }); + return new EditorView({ state }); + } + + function getDocText(cmView: EditorView) { + return cmView.state.doc.toString(); + } + + function getCursor(cmView: EditorView) { + return cmView.state.selection.main.head; + } + + it('formats JavaScript correctly and keeps cursor stable', () => { + const messyJs = `function foo(){console.log("hi")}`; + const cmView = createEditor(messyJs); + cmView.dispatch({ selection: { anchor: 14 } }); + + tidyCodeWithPrettier(cmView, 'javascript'); + + const expectedJs = 'function foo() {\n console.log("hi");\n}\n'; + expect(getDocText(cmView)).toBe(expectedJs); + + const newCursor = getCursor(cmView); + expect(newCursor).toBeGreaterThan(0); + }); + + it('formats HTML correctly and keeps cursor stable', () => { + const messyHtml = `hello`; + const cmView = createEditor(messyHtml); + tidyCodeWithPrettier(cmView, 'html'); + + const expectedHtml = '\n \n hello\n \n\n'; + expect(getDocText(cmView)).toBe(expectedHtml); + }); + + it('formats CSS correctly and keeps cursor stable', () => { + const messyCss = `body{margin:0;padding:0;}`; + const cmView = createEditor(messyCss); + + tidyCodeWithPrettier(cmView, 'css'); + + const expectedCss = 'body {\n margin: 0;\n padding: 0;\n}\n'; + expect(getDocText(cmView)).toBe(expectedCss); + }); + + it('handles an empty document without errors', () => { + const cmView = createEditor(''); + expect(() => tidyCodeWithPrettier(cmView, 'javascript')).not.toThrow(); + expect(getDocText(cmView)).toBe(''); + }); + + it('leaves the cursor unmoved if the code is already formatted', () => { + const cleanCode = 'function foo() {}\n'; + const cmView = createEditor(cleanCode); + cmView.dispatch({ selection: { anchor: 5 } }); + + tidyCodeWithPrettier(cmView, 'javascript'); + + expect(getCursor(cmView)).toBe(5); + }); +}); diff --git a/client/modules/IDE/components/Editor/tidier.ts b/client/modules/IDE/components/Editor/tidier.ts new file mode 100644 index 0000000000..41fdb24572 --- /dev/null +++ b/client/modules/IDE/components/Editor/tidier.ts @@ -0,0 +1,44 @@ +import prettier from 'prettier/standalone'; +import babelParser from 'prettier/parser-babel'; +import htmlParser from 'prettier/parser-html'; +import cssParser from 'prettier/parser-postcss'; +import type { EditorView } from '@codemirror/view'; +import type { Plugin } from 'prettier'; + +type ParserTypes = 'babel' | 'html' | 'css'; +type FormatMode = 'html' | 'css' | 'javascript'; + +function prettierFormatWithCursor( + parser: ParserTypes, + plugins: Plugin[], + cmView: EditorView +) { + const { doc } = cmView.state; + const cursorOffset = cmView.state.selection.main.head; + const { + formatted, + cursorOffset: newCursorOffset + } = prettier.formatWithCursor(doc.toString(), { + cursorOffset, + parser, + plugins + }); + + cmView.dispatch({ + changes: { from: 0, to: doc.length, insert: formatted }, + selection: { anchor: newCursorOffset } + }); + + cmView.focus(); +} + +/** Runs prettier on the codemirror instance, depending on the mode. */ +export function tidyCodeWithPrettier(cmView: EditorView, mode: FormatMode) { + if (mode === 'javascript') { + prettierFormatWithCursor('babel', [babelParser], cmView); + } else if (mode === 'css') { + prettierFormatWithCursor('css', [cssParser], cmView); + } else if (mode === 'html') { + prettierFormatWithCursor('html', [htmlParser], cmView); + } +} diff --git a/client/modules/IDE/components/Header/MobileNav.jsx b/client/modules/IDE/components/Header/MobileNav.jsx index 2ef0ff3026..b055b62be8 100644 --- a/client/modules/IDE/components/Header/MobileNav.jsx +++ b/client/modules/IDE/components/Header/MobileNav.jsx @@ -427,7 +427,7 @@ const MoreMenu = () => { {t('Nav.Edit.TidyCode')} - + {t('Nav.Edit.Find')} {t('Nav.Sketch.Title')} diff --git a/client/modules/IDE/components/Header/Nav.jsx b/client/modules/IDE/components/Header/Nav.jsx index 48e0935ae9..0ca5f67961 100644 --- a/client/modules/IDE/components/Header/Nav.jsx +++ b/client/modules/IDE/components/Header/Nav.jsx @@ -169,8 +169,6 @@ const ProjectMenu = () => { shareSketch } = useSketchActions(); - const replaceCommand = - metaKey === 'Ctrl' ? `${metaKeyName}+H` : `${metaKeyName}+⌥+F`; const newFileCommand = metaKey === 'Ctrl' ? `${metaKeyName}+Alt+N` : `${metaKeyName}+⌥+N`; @@ -268,14 +266,10 @@ const ProjectMenu = () => { {t('Nav.Edit.TidyCode')} {metaKeyName}+Shift+F - + {t('Nav.Edit.Find')} {metaKeyName}+F - - {t('Nav.Edit.Replace')} - {replaceCommand} - -
  • - Replace - - Ctrl+H - -
  • { @@ -15,8 +16,11 @@ const Pagination = ({ const { t } = useTranslation(); - const startSketch = (page - 1) * limit + 1; - const endSketch = Math.min(page * limit, totalSketches); + const totalItems = Number.isFinite(totalSketches) + ? totalSketches + : totalCollections; + const startItem = totalItems > 0 ? (page - 1) * limit + 1 : 0; + const endItem = totalItems > 0 ? Math.min(page * limit, totalItems) : 0; return (
    @@ -35,11 +39,12 @@ const Pagination = ({
  • - {startSketch} - {endSketch} + {startItem} - {endItem} {' '} - {t('Pagination.Of')} {totalSketches} + {t('Pagination.Of')} {totalItems}
  • +
  • 1) return; - // By default, don't allow completion when something is selected. - // A hint function can have a `supportsSelection` property to - // indicate that it can handle selections. - if (this.somethingSelected()) { - if (!options.hint.supportsSelection) return; - // Don't try with cross-line selections - // if selection spans multiple lines, bail out - for (var i = 0; i < selections.length; i++) - if (selections[i].head.line != selections[i].anchor.line) return; - } - - if (this.state.completionActive) this.state.completionActive.close(); // close an already active autocomplete session if active - // create a new completion object and saves it to this.state.completionActive - var completion = (this.state.completionActive = new Completion( - this, - options - )); - if (!completion.options.hint) return; // safety check to ensure hint is valid - - CodeMirror.signal(this, 'startCompletion', this); // emits a signal; fires a startCompletion event on editor instance - completion.update(true); - }); - - CodeMirror.defineExtension('closeHint', function () { - if (this.state.completionActive) this.state.completionActive.close(); - }); - - // defines a constructor function - function Completion(cm, options) { - this.cm = cm; - this.options = options; - this.widget = null; // will hold a reference to the dropdown menu that shows suggestions - this.debounce = 0; - this.tick = 0; - this.startPos = this.cm.getCursor('start'); // startPos is a {line,ch} object used to remember where hinting started - // startLen is the len of the line minus length of any selected text - this.startLen = - this.cm.getLine(this.startPos.line).length - - this.cm.getSelection().length; - - if (this.options.updateOnCursorActivity) { - var self = this; // stores ref to this as self so it can be accessed inside the nested function - // adds an event listener to the editor; called when the cursor moves - cm.on( - 'cursorActivity', - (this.activityFunc = function () { - self.cursorActivity(); - }) - ); - } - } - - var requestAnimationFrame = - window.requestAnimationFrame || - function (fn) { - return setTimeout(fn, 1000 / 60); - }; - var cancelAnimationFrame = window.cancelAnimationFrame || clearTimeout; - - Completion.prototype = { - close: function () { - if (!this.active()) return; - this.cm.state.completionActive = null; - this.tick = null; - // removes the current activity listener - if (this.options.updateOnCursorActivity) { - this.cm.off('cursorActivity', this.activityFunc); - } - // signals and removes the widget - if (this.widget && this.data) CodeMirror.signal(this.data, 'close'); - if (this.widget) this.widget.close(); - // emits a completition end event - CodeMirror.signal(this.cm, 'endCompletion', this.cm); - }, - - active: function () { - return this.cm.state.completionActive == this; - }, - - pick: function (data, i) { - // selects an item from the suggestion list - var completion = data.list[i], - self = this; - - this.cm.operation(function () { - const name = completion.item?.text; - - if (completion.hint) { - completion.hint(self.cm, data, completion); - } else { - self.cm.replaceRange( - getText(completion), - completion.from || data.from, - completion.to || data.to, - 'complete' - ); - } - // signals that a hint was picked and scrolls to it - CodeMirror.signal(data, 'pick', completion); - self.cm.scrollIntoView(); - }); - // closes widget if closeOnPick is enabled - if (this.options.closeOnPick) { - this.close(); - } - }, - - cursorActivity: function () { - // if a debounce is scheduled, cancel it to avoid outdated updates - if (this.debounce) { - cancelAnimationFrame(this.debounce); - this.debounce = 0; - } - - var identStart = this.startPos; - if (this.data) { - identStart = this.data.from; - } - - var pos = this.cm.getCursor(), - line = this.cm.getLine(pos.line); - if ( - pos.line != this.startPos.line || - line.length - pos.ch != this.startLen - this.startPos.ch || - pos.ch < identStart.ch || - this.cm.somethingSelected() || - !pos.ch || - this.options.closeCharacters.test(line.charAt(pos.ch - 1)) - ) { - this.close(); - } else { - var self = this; - this.debounce = requestAnimationFrame(function () { - self.update(); - }); - if (this.widget) this.widget.disable(); - } - }, - - update: function (first) { - if (this.tick == null) return; - var self = this, - myTick = ++this.tick; - fetchHints(this.options.hint, this.cm, this.options, function (data) { - if (self.tick == myTick) self.finishUpdate(data, first); - }); - }, - - finishUpdate: function (data, first) { - if (this.data) CodeMirror.signal(this.data, 'update'); - - var picked = - (this.widget && this.widget.picked) || - (first && this.options.completeSingle); - if (this.widget) this.widget.close(); - - this.data = data; - - if (data && data.list.length) { - if (picked && data.list.length == 1) { - this.pick(data, 0); - } else { - this.widget = new Widget(this, data); - CodeMirror.signal(data, 'shown'); - } - } - } - }; - - function parseOptions(cm, pos, options) { - var editor = cm.options.hintOptions; - var out = {}; - // copies all default hint settings into out - for (var prop in defaultOptions) out[prop] = defaultOptions[prop]; - if (editor) - for (var prop in editor) - if (editor[prop] !== undefined) out[prop] = editor[prop]; - if (options) - for (var prop in options) - if (options[prop] !== undefined) out[prop] = options[prop]; - if (out.hint.resolve) out.hint = out.hint.resolve(cm, pos); - return out; - } - // extracts the visible text from a completion entry - function getText(completion) { - if (typeof completion === 'string') return completion; - else return completion.item.text; - } - - // builds a key mapping object to define keyboard behavior for autocomplete - function buildKeyMap(completion, handle) { - var baseMap = { - Up: function () { - handle.moveFocus(-1); - }, - Down: function () { - handle.moveFocus(1); - }, - PageUp: function () { - handle.moveFocus(-handle.menuSize() + 1, true); - }, - PageDown: function () { - handle.moveFocus(handle.menuSize() - 1, true); - }, - Home: function () { - handle.setFocus(0); - }, - End: function () { - handle.setFocus(handle.length - 1); - }, - Enter: handle.pick, - Tab: handle.pick, - Esc: handle.close - }; - // checks if the user is on macOS and adds shortcuts accordingly - var mac = /Mac/.test(navigator.platform); - - if (mac) { - baseMap['Ctrl-P'] = function () { - handle.moveFocus(-1); - }; - baseMap['Ctrl-N'] = function () { - handle.moveFocus(1); - }; - } - - // user defined custom key bindings - var custom = completion.options.customKeys; - var ourMap = custom ? {} : baseMap; - function addBinding(key, val) { - var bound; - if (typeof val != 'string') - bound = function (cm) { - return val(cm, handle); - }; - // This mechanism is deprecated - else if (baseMap.hasOwnProperty(val)) bound = baseMap[val]; - else bound = val; - ourMap[key] = bound; - } - // apply all custom key bindings and extraKeys - if (custom) - for (var key in custom) - if (custom.hasOwnProperty(key)) addBinding(key, custom[key]); - var extra = completion.options.extraKeys; - if (extra) - for (var key in extra) - if (extra.hasOwnProperty(key)) addBinding(key, extra[key]); - return ourMap; - } - - // hintsElement is the parent for hints and el is the clicked element within that container - function displayHint(name, type, p5, isBlacklistedFunction, referenceBaseUrl) { - const base = referenceBaseUrl || 'https://p5js.org'; - const refName = typeof p5 === 'string' ? p5 : name; - - const linkOrPlaceholder = p5 - ? ` - open ${name} reference - - ` - : ` - no reference for ${name} - `; - - const hintHTML = `
    - ${name} - ${type} - ${linkOrPlaceholder} -
    `; - - if (isBlacklistedFunction) { - return `
    - ${hintHTML} -
    ⚠️use with caution in this context
    -
    `; - } else { - return `
    ${hintHTML}
    `; - } - } - - - function getInlineHintSuggestion(cm, focus, token) { - let tokenLength = token.string.length; - if (token.string === '.') { - tokenLength -= 1; - } - const name = focus.item?.text; - - const suggestionItem = focus.item; - // builds the remainder of the suggestion excluding what user already typed - const baseCompletion = `${suggestionItem.text.slice( - tokenLength - )}`; - if (suggestionItem.type !== 'fun') return baseCompletion; - - // for functions - return ( - baseCompletion + - '(' + - (suggestionItem.params && suggestionItem.params.length - ? suggestionItem.params.map(({ p, o }) => (o ? `[${p}]` : p)).join(', ') - : '') + - ')' - ); - } - - // clears existing inline hint (like the part is suggested) - function removeInlineHint(cm) { - if (cm.state.inlineHint) { - cm.state.inlineHint.clear(); - cm.state.inlineHint = null; - } - } - - function changeInlineHint(cm, focus) { - removeInlineHint(cm); - - const cursor = cm.getCursor(); - const token = cm.getTokenAt(cursor); - - if (token && focus.item) { - const suggestionHTML = getInlineHintSuggestion(cm, focus, token); - - const widgetElement = document.createElement('span'); - widgetElement.className = 'autocomplete-inline-hinter'; - widgetElement.innerHTML = suggestionHTML; - - const widget = cm.setBookmark(cursor, { widget: widgetElement }); - cm.state.inlineHint = widget; - cm.setCursor(cursor); - } - } - - // defines the autocomplete dropdown ui; renders the suggestions - // completion = the autocomplete context having cm and options - // data = object with the list of suggestions - function Widget(completion, data) { - this.id = 'cm-complete-' + Math.floor(Math.random(1e6)); - this.completion = completion; - this.data = data; - this.picked = false; - var widget = this, - cm = completion.cm; - var ownerDocument = cm.getInputField().ownerDocument; - var parentWindow = ownerDocument.defaultView || ownerDocument.parentWindow; - - var fontSize = completion.options._fontSize; - - var hints = (this.hints = ownerDocument.createElement('ul')); - hints.setAttribute('role', 'listbox'); - hints.setAttribute('aria-expanded', 'true'); - hints.id = this.id; - var theme = completion.cm.options.theme; - hints.className = 'CodeMirror-hints ' + theme; - this.selectedHint = data.selectedHint || 0; - - const referenceBaseUrl = completion.options.referenceBaseUrl || 'https://p5js.org'; - - // Show inline hint - changeInlineHint(cm, data.list[this.selectedHint]); - - var completions = data.list; - for (var i = 0; i < completions.length; ++i) { - const cur = completions[i]; - - const elt = ownerDocument.createElement('li'); - elt.className = - HINT_ELEMENT_CLASS + - (i !== this.selectedHint ? '' : ' ' + ACTIVE_HINT_ELEMENT_CLASS) + - (cur.isBlacklisted ? ' blacklisted' : ''); - - if (cur.className != null) - elt.className = cur.className + ' ' + elt.className; - - if (i === this.selectedHint) elt.setAttribute('aria-selected', 'true'); - elt.id = this.id + '-' + i; - elt.setAttribute('role', 'option'); - elt.hintId = i; - - if (cur.render) { - cur.render(elt, data, cur); - } else { - const name = getText(cur); - if (cur.item && cur.item.type) { - cur.displayText = displayHint( - name, - cur.item.type, - cur.item.p5, - cur.isBlacklisted, - referenceBaseUrl - ); - } - - elt.innerHTML = - cur.displayText || `${name}`; - } - - hints.appendChild(elt); - } - - var container = completion.options.container || ownerDocument.body; - var pos = cm.cursorCoords( - completion.options.alignWithWord ? data.from : null - ); - var left = pos.left, - top = pos.bottom, - below = true; - var offsetLeft = 0, - offsetTop = 0; - if (container !== ownerDocument.body) { - // We offset the cursor position because left and top are relative to the offsetParent's top left corner. - var isContainerPositioned = - ['absolute', 'relative', 'fixed'].indexOf( - parentWindow.getComputedStyle(container).position - ) !== -1; - var offsetParent = isContainerPositioned - ? container - : container.offsetParent; - var offsetParentPosition = offsetParent.getBoundingClientRect(); - var bodyPosition = ownerDocument.body.getBoundingClientRect(); - offsetLeft = - offsetParentPosition.left - bodyPosition.left - offsetParent.scrollLeft; - offsetTop = - offsetParentPosition.top - bodyPosition.top - offsetParent.scrollTop; - } - hints.style.left = left - offsetLeft + 'px'; - hints.style.top = top - offsetTop + 'px'; - - // If we're at the edge of the screen, then we want the menu to appear on the left of the cursor. - var winW = - parentWindow.innerWidth || - Math.max( - ownerDocument.body.offsetWidth, - ownerDocument.documentElement.offsetWidth - ); - var winH = - parentWindow.innerHeight || - Math.max( - ownerDocument.body.offsetHeight, - ownerDocument.documentElement.offsetHeight - ); - container.appendChild(hints); - cm.getInputField().setAttribute('aria-autocomplete', 'list'); - cm.getInputField().setAttribute('aria-owns', this.id); - cm.getInputField().setAttribute( - 'aria-activedescendant', - this.id + '-' + this.selectedHint - ); - - var box = completion.options.moveOnOverlap - ? hints.getBoundingClientRect() - : new DOMRect(); - var scrolls = completion.options.paddingForScrollbar - ? hints.scrollHeight > hints.clientHeight + 1 - : false; - - // Compute in the timeout to avoid reflow on init - var startScroll; - setTimeout(function () { - startScroll = cm.getScrollInfo(); - }); - - var overlapY = box.bottom - winH; - if (overlapY > 0) { - var height = box.bottom - box.top, - curTop = pos.top - (pos.bottom - box.top); - if (curTop - height > 0) { - // Fits above cursor - hints.style.top = (top = pos.top - height - offsetTop) + 'px'; - below = false; - } else if (height > winH) { - hints.style.height = winH - 5 + 'px'; - hints.style.top = (top = pos.bottom - box.top - offsetTop) + 'px'; - var cursor = cm.getCursor(); - if (data.from.ch != cursor.ch) { - pos = cm.cursorCoords(cursor); - hints.style.left = (left = pos.left - offsetLeft) + 'px'; - box = hints.getBoundingClientRect(); - } - } - } - var overlapX = box.right - winW; - if (scrolls) overlapX += cm.display.nativeBarWidth; - if (overlapX > 0) { - if (box.right - box.left > winW) { - hints.style.width = winW - 5 + 'px'; - overlapX -= box.right - box.left - winW; - } - hints.style.left = (left = pos.left - overlapX - offsetLeft) + 'px'; - } - // if (scrolls) for (var node = hints.firstChild; node; node = node.nextSibling) - // node.style.paddingRight = cm.display.nativeBarWidth + "px" - - cm.addKeyMap( - (this.keyMap = buildKeyMap(completion, { - moveFocus: function (n, avoidWrap) { - return widget.changeActive(widget.selectedHint + n, avoidWrap); - }, - setFocus: function (n) { - return widget.changeActive(n); - }, - menuSize: function () { - return widget.screenAmount(); - }, - length: completions.length, - close: function () { - completion.close(); - }, - pick: function () { - widget.pick(); - }, - data: data - })) - ); - - if (completion.options.closeOnUnfocus) { - var closingOnBlur; - cm.on( - 'blur', - (this.onBlur = function () { - closingOnBlur = setTimeout(function () { - completion.close(); - }, 100); - }) - ); - cm.on( - 'focus', - (this.onFocus = function () { - clearTimeout(closingOnBlur); - }) - ); - } - - cm.on( - 'scroll', - (this.onScroll = function () { - var curScroll = cm.getScrollInfo(), - editor = cm.getWrapperElement().getBoundingClientRect(); - if (!startScroll) startScroll = cm.getScrollInfo(); - var newTop = top + startScroll.top - curScroll.top; - var point = - newTop - - (parentWindow.pageYOffset || - (ownerDocument.documentElement || ownerDocument.body).scrollTop); - if (!below) point += hints.offsetHeight; - if (point <= editor.top || point >= editor.bottom) - return completion.close(); - hints.style.top = newTop + 'px'; - hints.style.left = left + startScroll.left - curScroll.left + 'px'; - }) - ); - - function getHintElement(container, el) { - while (el && el !== container && el.hintId == null) { - el = el.parentNode; - } - return el; - } - - CodeMirror.on(hints, 'dblclick', function (e) { - var t = getHintElement(hints, e.target || e.srcElement); - if (t && t.hintId != null) { - widget.changeActive(t.hintId); - widget.pick(); - } - }); - - CodeMirror.on(hints, 'click', function (e) { - var t = getHintElement(hints, e.target || e.srcElement); - if (t && t.hintId != null) { - widget.changeActive(t.hintId); - if (completion.options.completeOnSingleClick) widget.pick(); - } - }); - - CodeMirror.on(hints, 'mousedown', function () { - setTimeout(function () { - cm.focus(); - }, 20); - }); - - // The first hint doesn't need to be scrolled to on init - var selectedHintRange = this.getSelectedHintRange(); - if (selectedHintRange.from !== 0 || selectedHintRange.to !== 0) { - this.scrollToActive(); - } - - CodeMirror.signal( - data, - 'select', - completions[this.selectedHint], - hints.childNodes[this.selectedHint] - ); - return true; - } - - Widget.prototype = { - close: function () { - if (this.completion.widget != this) return; - this.completion.widget = null; - if (this.hints.parentNode) this.hints.parentNode.removeChild(this.hints); - this.completion.cm.removeKeyMap(this.keyMap); - var input = this.completion.cm.getInputField(); - input.removeAttribute('aria-activedescendant'); - input.removeAttribute('aria-owns'); - - var cm = this.completion.cm; - if (this.completion.options.closeOnUnfocus) { - cm.off('blur', this.onBlur); - cm.off('focus', this.onFocus); - } - cm.off('scroll', this.onScroll); - - removeInlineHint(cm); - }, - - disable: function () { - this.completion.cm.removeKeyMap(this.keyMap); - var widget = this; - this.keyMap = { - Enter: function () { - widget.picked = true; - } - }; - this.completion.cm.addKeyMap(this.keyMap); - }, - - pick: function () { - this.completion.pick(this.data, this.selectedHint); - }, - - changeActive: function (i, avoidWrap) { - if (i >= this.data.list.length) - i = avoidWrap ? this.data.list.length - 1 : 0; - else if (i < 0) i = avoidWrap ? 0 : this.data.list.length - 1; - - if (this.selectedHint == i) { - changeInlineHint(this.completion.cm, this.data.list[this.selectedHint]); - return this.data.list[this.selectedHint]; - } - - var node = this.hints.childNodes[this.selectedHint]; - if (node) { - node.className = node.className.replace( - ' ' + ACTIVE_HINT_ELEMENT_CLASS, - '' - ); - node.removeAttribute('aria-selected'); - } - node = this.hints.childNodes[(this.selectedHint = i)]; - node.className += ' ' + ACTIVE_HINT_ELEMENT_CLASS; - node.setAttribute('aria-selected', 'true'); - this.completion.cm - .getInputField() - .setAttribute('aria-activedescendant', node.id); - this.scrollToActive(); - CodeMirror.signal( - this.data, - 'select', - this.data.list[this.selectedHint], - node - ); - - changeInlineHint(this.completion.cm, this.data.list[this.selectedHint]); - return this.data.list[this.selectedHint]; - }, - - scrollToActive: function () { - var selectedHintRange = this.getSelectedHintRange(); - var node1 = this.hints.childNodes[selectedHintRange.from]; - var node2 = this.hints.childNodes[selectedHintRange.to]; - var firstNode = this.hints.firstChild; - if (node1.offsetTop < this.hints.scrollTop) - this.hints.scrollTop = node1.offsetTop - firstNode.offsetTop; - else if ( - node2.offsetTop + node2.offsetHeight > - this.hints.scrollTop + this.hints.clientHeight - ) - this.hints.scrollTop = - node2.offsetTop + - node2.offsetHeight - - this.hints.clientHeight + - firstNode.offsetTop; - }, - - screenAmount: function () { - return ( - Math.floor( - this.hints.clientHeight / this.hints.firstChild.offsetHeight - ) || 1 - ); - }, - - getSelectedHintRange: function () { - var margin = this.completion.options.scrollMargin || 0; - return { - from: Math.max(0, this.selectedHint - margin), - to: Math.min(this.data.list.length - 1, this.selectedHint + margin) - }; - } - }; - - function applicableHelpers(cm, helpers) { - if (!cm.somethingSelected()) return helpers; - var result = []; - for (var i = 0; i < helpers.length; i++) - if (helpers[i].supportsSelection) result.push(helpers[i]); - return result; - } - - function fetchHints(hint, cm, options, callback) { - if (hint.async) { - hint(cm, callback, options); - } else { - var result = hint(cm, options); - if (result && result.then) result.then(callback); - else callback(result); - } - } - - function resolveAutoHints(cm, pos) { - var helpers = cm.getHelpers(pos, 'hint'), - words; - if (helpers.length) { - var resolved = function (cm, callback, options) { - var app = applicableHelpers(cm, helpers); - function run(i) { - if (i == app.length) return callback(null); - fetchHints(app[i], cm, options, function (result) { - if (result && result.list.length > 0) callback(result); - else run(i + 1); - }); - } - run(0); - }; - resolved.async = true; - resolved.supportsSelection = true; - return resolved; - } else if ((words = cm.getHelper(cm.getCursor(), 'hintWords'))) { - return function (cm) { - return CodeMirror.hint.fromList(cm, { words: words }); - }; - } else if (CodeMirror.hint.anyword) { - return function (cm, options) { - return CodeMirror.hint.anyword(cm, options); - }; - } else { - return function () {}; - } - } - - CodeMirror.registerHelper('hint', 'auto', { - resolve: resolveAutoHints - }); - - CodeMirror.registerHelper('hint', 'fromList', function (cm, options) { - var cur = cm.getCursor(), - token = cm.getTokenAt(cur); - var term, - from = CodeMirror.Pos(cur.line, token.start), - to = cur; - if ( - token.start < cur.ch && - /\w/.test(token.string.charAt(cur.ch - token.start - 1)) - ) { - term = token.string.substr(0, cur.ch - token.start); - } else { - term = ''; - from = cur; - } - var found = []; - for (var i = 0; i < options.words.length; i++) { - var word = options.words[i]; - if (word.slice(0, term.length) == term) found.push(word); - } - - if (found.length) return { list: found, from: from, to: to }; - }); - - CodeMirror.commands.autocomplete = CodeMirror.showHint; - - var defaultOptions = { - hint: CodeMirror.hint.auto, - completeSingle: true, - alignWithWord: true, - closeCharacters: /[\s()\[\]{};:>,]/, - closeOnPick: true, - closeOnUnfocus: true, - updateOnCursorActivity: true, - completeOnSingleClick: true, - container: null, - customKeys: null, - extraKeys: null, - paddingForScrollbar: true, - moveOnOverlap: true - }; - - CodeMirror.defineOption('hintOptions', null); -}); diff --git a/client/modules/IDE/reducers/CollectionsListCollection.js b/client/modules/IDE/reducers/CollectionsListCollection.js new file mode 100644 index 0000000000..4a347f7255 --- /dev/null +++ b/client/modules/IDE/reducers/CollectionsListCollection.js @@ -0,0 +1,47 @@ +import * as ActionTypes from '../../../constants'; + +const initialState = { + collections: [], + metadata: { + page: 1, + totalPages: 1, + totalCollections: 0, + limit: 10, + hasPagination: false + } +}; + +export default function collectionsListCollections( + state = initialState, + action +) { + switch (action.type) { + case ActionTypes.SET_COLLECTIONS_FOR_COLLECTION_LIST: + return { + ...state, + collections: action.collections?.collections ?? [], + metadata: action.collections?.metadata ?? initialState.metadata + }; + + case ActionTypes.DELETE_COLLECTION: + return { + ...state, + collections: state.collections.filter( + ({ id }) => action.collectionId !== id + ) + }; + + case ActionTypes.EDIT_COLLECTION: + case ActionTypes.ADD_TO_COLLECTION: + case ActionTypes.REMOVE_FROM_COLLECTION: + return { + ...state, + collections: state.collections.map((collection) => + collection.id === action.payload.id ? action.payload : collection + ) + }; + + default: + return state; + } +} diff --git a/client/modules/IDE/reducers/collections.js b/client/modules/IDE/reducers/collections.js index c7017c29a7..0aad556bea 100644 --- a/client/modules/IDE/reducers/collections.js +++ b/client/modules/IDE/reducers/collections.js @@ -1,25 +1,60 @@ import * as ActionTypes from '../../../constants'; -const sketches = (state = [], action) => { +const initialState = { + collections: [], + metadata: { + page: 1, + totalPages: 1, + totalCollections: 0, + limit: 10, + hasPagination: false + } +}; + +const normalizeSetCollections = (data) => { + if (data == null) { + return initialState; + } + if (Array.isArray(data)) { + return { + collections: data, + metadata: initialState.metadata + }; + } + return { + collections: data.collections ?? [], + metadata: data.metadata ?? initialState.metadata + }; +}; + +const sketches = (state = initialState, action) => { switch (action.type) { case ActionTypes.SET_COLLECTIONS: - return action.collections; + return normalizeSetCollections(action.collections); case ActionTypes.DELETE_COLLECTION: - return state.filter(({ id }) => action.collectionId !== id); + return { + ...state, + collections: state.collections.filter( + ({ id }) => action.collectionId !== id + ) + }; // The API returns the complete new edited collection // with any items added or removed case ActionTypes.EDIT_COLLECTION: case ActionTypes.ADD_TO_COLLECTION: case ActionTypes.REMOVE_FROM_COLLECTION: - return state.map((collection) => { - if (collection.id === action.payload.id) { - return action.payload; - } + return { + ...state, + collections: state.collections.map((collection) => { + if (collection.id === action.payload.id) { + return action.payload; + } - return collection; - }); + return collection; + }) + }; default: return state; } diff --git a/client/modules/IDE/reducers/ide.js b/client/modules/IDE/reducers/ide.js index 8d45b8b50a..64765cac5b 100644 --- a/client/modules/IDE/reducers/ide.js +++ b/client/modules/IDE/reducers/ide.js @@ -22,7 +22,6 @@ const initialState = { justOpenedProject: false, previousPath: '/', errorType: undefined, - runtimeErrorWarningVisible: false, parentId: undefined }; @@ -111,10 +110,6 @@ const ide = (state = initialState, action) => { return Object.assign({}, state, { errorType: action.modalType }); case ActionTypes.HIDE_ERROR_MODAL: return Object.assign({}, state, { errorType: undefined }); - case ActionTypes.HIDE_RUNTIME_ERROR_WARNING: - return Object.assign({}, state, { runtimeErrorWarningVisible: false }); - case ActionTypes.SHOW_RUNTIME_ERROR_WARNING: - return Object.assign({}, state, { runtimeErrorWarningVisible: true }); case ActionTypes.OPEN_UPLOAD_FILE_MODAL: return Object.assign({}, state, { uploadFileModalVisible: true, diff --git a/client/modules/IDE/selectors/collections.js b/client/modules/IDE/selectors/collections.js index 02b38305f0..23b865aeaf 100644 --- a/client/modules/IDE/selectors/collections.js +++ b/client/modules/IDE/selectors/collections.js @@ -3,7 +3,15 @@ import differenceInMilliseconds from 'date-fns/differenceInMilliseconds'; import { find, orderBy } from 'lodash'; import { DIRECTION } from '../actions/sorting'; -const getCollections = (state) => state.collections; +/** List API returns `{ collections, metadata }`; this slice may also be a legacy plain array. */ +function collectionsFromSlice(slice) { + if (slice == null) return []; + if (Array.isArray(slice)) return slice; + if (Array.isArray(slice.collections)) return slice.collections; + return []; +} + +const getCollections = (state) => collectionsFromSlice(state.collections); const getField = (state) => state.sorting.field; const getDirection = (state) => state.sorting.direction; const getSearchTerm = (state) => state.search.collectionSearchTerm; diff --git a/client/reducers.ts b/client/reducers.ts index 8e5ed7b67d..607adcc991 100644 --- a/client/reducers.ts +++ b/client/reducers.ts @@ -13,6 +13,7 @@ import search from './modules/IDE/reducers/search'; import sorting from './modules/IDE/reducers/sorting'; import loading from './modules/IDE/reducers/loading'; import collections from './modules/IDE/reducers/collections'; +import collectionsListCollections from './modules/IDE/reducers/CollectionsListCollection'; import collectionsListProjects from './modules/IDE/reducers/collectionsListProjects'; const rootReducer = combineReducers({ @@ -30,6 +31,7 @@ const rootReducer = combineReducers({ assets, loading, collections, + collectionsListCollections, collectionsListProjects }); diff --git a/client/styles/abstracts/_variables.scss b/client/styles/abstracts/_variables.scss index b1eebc3011..df0189462f 100644 --- a/client/styles/abstracts/_variables.scss +++ b/client/styles/abstracts/_variables.scss @@ -6,33 +6,68 @@ $p5js-pink-opacity: #ed225d80; $p5js-active-pink: #f10046; $white: #fff; $black: #000; -$yellow: #F5DC23; -$dodgerblue: #1E90FF; -$p5-contrast-pink: #FFA9D9; +$yellow: #f5dc23; +$dodgerblue: #1e90ff; +$p5-contrast-pink: #ffa9d9; -$outline-color: #0F9DD7; +$outline-color: #0f9dd7; // Grayscale values -$lightest: #FFF; // primary -$lighter: #FBFBFB; +$lightest: #fff; // primary +$lighter: #fbfbfb; -$light: #F0F0F0; // primary -$medium-light: #D9D9D9; -$middle-light: #A6A6A6; +$light: #f0f0f0; // primary +$medium-light: #d9d9d9; +$middle-light: #a6a6a6; // $middle-gray: #7D7D7D; // primary $middle-gray: #747474; // primary $middle-dark: #666; -$medium-dark: #4D4D4D; +$medium-dark: #4d4d4d; $dark: #333; // primary -$darker: #1C1C1C; +$darker: #1c1c1c; $darkest: #000; +// Light highlight styles +$p5-light-selected: $medium-light; +$p5-light-activeline: #cfcfcf44; +$p5-light-brown: #7a5a3a; +$p5-light-black: #333333; +$p5-light-pink: #d52889; +$p5-light-gray: #666; +$p5-light-blue: #0b7ca9; +$p5-light-orange: #a06801; +$p5-light-lightgray: $middle-gray; +$p5-light-green: #47820a; + +// Dark highlight styles +$p5-dark-selected: $medium-dark; +$p5-dark-activeline: #cfcfcf22; +$p5-dark-pink: #de4a9b; +$p5-dark-gray: #9b9b9b; +$p5-dark-lightblue: #0f9dd7; +$p5-dark-white: #fdfdfd; +$p5-dark-orange: #ee9900; +$p5-dark-green: #58a10b; +$p5-dark-goldbrown: #b58318; + +// Contrast highlight styles +$p5-contrast-selected: $middle-dark; +$p5-contrast-activeline: #333333aa; +$p5-contrast-white: #fdfdfd; +$p5-contrast-lightgray: #c1c1c1; +$p5-contrast-blue: #00ffff; +$p5-contrast-green: #2de9b6; +$p5-contrast-yellow: #f5dc23; +$p5-contrast-orange: #ffa95d; +$p5-contrast-pink: #ffa9d9; + $themes: ( light: ( - admonition-background: #E4F8FF, - admonition-border: #22C8ED, + activeline-background-color: $p5-light-activeline, + admonition-background: #e4f8ff, + admonition-border: #22c8ed, admonition-text: #075769, background-color: $lighter, button-active-color: $lightest, @@ -44,9 +79,9 @@ $themes: ( button-hover-color: $lightest, button-nav-inactive-color: $middle-gray, button-secondary-background-color: $medium-light, - codefold-icon-closed: url("../images/triangle-arrow-right.svg?byUrl"), - codefold-icon-open: url("../images/triangle-arrow-down.svg?byUrl"), - console-active-arrow-color: #0071AD, + codefold-icon-closed: url('../images/triangle-arrow-right.svg?byUrl'), + codefold-icon-open: url('../images/triangle-arrow-down.svg?byUrl'), + console-active-arrow-color: #0071ad, console-arrow-color: $middle-gray, console-background-color: $light, console-color: $darker, @@ -63,13 +98,26 @@ $themes: ( form-secondary-title-color: $medium-dark, form-title-color: rgba(51, 51, 51, 0.87), heavy-text-color: $darker, + highlight-style-comment-color: $p5-light-lightgray, + highlight-style-variable-color: $p5-light-blue, + highlight-style-string-color: $p5-light-green, + highlight-style-regexp-color: $p5-light-orange, + highlight-style-number-color: $p5-light-black, + highlight-style-atom-color: $p5-light-pink, + highlight-style-keyword-color: $p5-light-brown, + highlight-style-operator-color: $p5-light-brown, + highlight-style-def-color: $p5-light-blue, + highlight-style-tag-color: $p5-light-pink, + highlight-style-property-color: $p5-light-black, + highlight-style-attribute-color: $p5-light-black, + highlight-style-matching-selection-background-color: $p5js-pink-opacity, hint-arrow-background-active-color: $p5js-active-pink, hint-arrow-background-color: #ed225ddd, hint-arrow-color: $lightest, hint-arrow-focus-outline-color: $middle-dark, hint-background-color: $white, - hint-fun-active-border-bottom-color: #0B7CA9, - hint-fun-text-color: #0B7CA9, + hint-fun-active-border-bottom-color: #0b7ca9, + hint-fun-text-color: #0b7ca9, hint-inline-text-color-light: $middle-light, hint-inline-text-color: $middle-gray, hint-item-active-background-color: $middle-gray, @@ -79,12 +127,12 @@ $themes: ( hint-item-active-type-text-color: $white, hint-item-border-bottom-color: $white, hint-item-hover-background-color: #f4f4f4, - hint-keyword-text-color: #7A5A3A, + hint-keyword-text-color: #7a5a3a, hint-no-link-background-color: $medium-light, hint-text-color: $dark, hint-type-text-color: $medium-dark, - hint-var-active-border-bottom-color: #D52889, - hint-var-text-color: #D52889, + hint-var-active-border-bottom-color: #d52889, + hint-var-text-color: #d52889, icon-color: $middle-gray, icon-hover-color: $darker, icon-toast-hover-color: $lightest, @@ -114,9 +162,10 @@ $themes: ( search-hover-background-color: $medium-dark, search-hover-text-color: $lightest, secondary-text-color: $medium-dark, + selected-background-color: $p5-light-selected, shadow-color: rgba(0, 0, 0, 0.16), table-button-active-color: $lightest, - table-button-background-active-color: #00A1D3, + table-button-background-active-color: #00a1d3, table-button-background-color: $middle-gray, table-button-background-hover-color: $p5js-pink, table-button-color: $lightest, @@ -126,12 +175,13 @@ $themes: ( toast-background-color: $medium-dark, toast-text-color: $lightest, toolbar-button-background-color: $medium-light, - toolbar-button-color: $dark, + toolbar-button-color: $dark ), dark: ( - admonition-background: #105A7F, - admonition-border: #22C8ED, - admonition-text: #FFFFFF, + activeline-background-color: $p5-dark-activeline, + admonition-background: #105a7f, + admonition-border: #22c8ed, + admonition-text: #ffffff, background-color: $darker, button-active-color: $lightest, button-background-active-color: $p5js-active-pink, @@ -142,9 +192,9 @@ $themes: ( button-hover-color: $lightest, button-nav-inactive-color: $middle-light, button-secondary-background-color: $medium-dark, - codefold-icon-closed: url("../images/triangle-arrow-right-white.svg?byUrl"), - codefold-icon-open: url("../images/triangle-arrow-down-white.svg?byUrl"), - console-active-arrow-color: #097BB3, + codefold-icon-closed: url('../images/triangle-arrow-right-white.svg?byUrl'), + codefold-icon-open: url('../images/triangle-arrow-down-white.svg?byUrl'), + console-active-arrow-color: #097bb3, console-arrow-color: $medium-light, console-background-color: $dark, console-color: $lightest, @@ -160,13 +210,26 @@ $themes: ( form-secondary-title-color: $medium-light, form-title-color: $lightest, heavy-text-color: $lightest, + highlight-style-comment-color: $p5-dark-gray, + highlight-style-variable-color: $p5-dark-lightblue, + highlight-style-string-color: $p5-dark-green, + highlight-style-regexp-color: $p5-dark-orange, + highlight-style-number-color: $p5-dark-white, + highlight-style-atom-color: $p5-dark-pink, + highlight-style-keyword-color: $p5-dark-goldbrown, + highlight-style-operator-color: $p5-dark-goldbrown, + highlight-style-def-color: $p5-dark-lightblue, + highlight-style-tag-color: $p5-dark-pink, + highlight-style-property-color: $p5-dark-white, + highlight-style-attribute-color: $p5-dark-lightblue, + highlight-style-matching-selection-background-color: $p5js-pink-opacity, hint-arrow-background-active-color: $p5js-active-pink, hint-arrow-background-color: #ed225ddd, hint-arrow-color: $lightest, hint-arrow-focus-outline-color: #cfcfcf, hint-background-color: $darker, - hint-fun-active-border-bottom-color: #0F9DD7, - hint-fun-text-color: #0F9DD7, + hint-fun-active-border-bottom-color: #0f9dd7, + hint-fun-text-color: #0f9dd7, hint-inline-text-color-light: $middle-gray, hint-inline-text-color: #cfcfcf, hint-item-active-background-color: #cfcfcf, @@ -176,12 +239,12 @@ $themes: ( hint-item-active-type-text-color: $darker, hint-item-border-bottom-color: $darker, hint-item-hover-background-color: $medium-dark, - hint-keyword-text-color: #B58318, + hint-keyword-text-color: #b58318, hint-no-link-background-color: $medium-dark, hint-text-color: $light, hint-type-text-color: $light, - hint-var-active-border-bottom-color: #DE4A9B, - hint-var-text-color: #DE4A9B, + hint-var-active-border-bottom-color: #de4a9b, + hint-var-text-color: #de4a9b, icon-color: $middle-light, icon-hover-color: $lightest, icon-toast-hover-color: $lightest, @@ -211,9 +274,10 @@ $themes: ( search-hover-background-color: $p5js-pink, search-hover-text-color: $lightest, secondary-text-color: $medium-light, + selected-background-color: $p5-dark-selected, shadow-color: rgba(0, 0, 0, 0.16), table-button-active-color: $lightest, - table-button-background-active-color: #00A1D3, + table-button-background-active-color: #00a1d3, table-button-background-color: $middle-gray, table-button-background-hover-color: $p5js-pink, table-button-color: $lightest, @@ -223,11 +287,12 @@ $themes: ( toast-background-color: $medium-light, toast-text-color: $dark, toolbar-button-background-color: $medium-dark, - toolbar-button-color: $lightest, + toolbar-button-color: $lightest ), contrast: ( + activeline-background-color: $p5-contrast-activeline, admonition-background: #000000, - admonition-border: #22C8ED, + admonition-border: #22c8ed, admonition-text: #ffffff, background-color: $darker, button-active-color: $dark, @@ -239,8 +304,8 @@ $themes: ( button-hover-color: $dark, button-nav-inactive-color: $light, button-secondary-background-color: $medium-dark, - codefold-icon-closed: url("../images/triangle-arrow-right-white.svg?byUrl"), - codefold-icon-open: url("../images/triangle-arrow-down-white.svg?byUrl"), + codefold-icon-closed: url('../images/triangle-arrow-right-white.svg?byUrl'), + codefold-icon-open: url('../images/triangle-arrow-down-white.svg?byUrl'), console-active-arrow-color: $dodgerblue, console-arrow-color: $lightest, console-background-color: $dark, @@ -256,13 +321,26 @@ $themes: ( form-secondary-title-color: $medium-light, form-title-color: $lightest, heavy-text-color: $yellow, - hint-arrow-background-active-color: #F5DC23, - hint-arrow-background-color: #F5DC23DD, + highlight-style-comment-color: $p5-contrast-lightgray, + highlight-style-variable-color: $p5-contrast-blue, + highlight-style-string-color: $p5-contrast-green, + highlight-style-regexp-color: $p5-contrast-green, + highlight-style-number-color: $p5-contrast-pink, + highlight-style-atom-color: $p5-contrast-pink, + highlight-style-keyword-color: $p5-contrast-yellow, + highlight-style-operator-color: $p5-contrast-lightgray, + highlight-style-def-color: $p5-contrast-blue, + highlight-style-tag-color: $p5-contrast-orange, + highlight-style-property-color: $p5-contrast-white, + highlight-style-attribute-color: $p5-contrast-white, + highlight-style-matching-selection-background-color: $medium-dark, + hint-arrow-background-active-color: #f5dc23, + hint-arrow-background-color: #f5dc23dd, hint-arrow-color: $darker, hint-arrow-focus-outline-color: $lighter, hint-background-color: $darkest, hint-fun-active-border-bottom-color: none, - hint-fun-text-color: #00FFFF, + hint-fun-text-color: #00ffff, hint-inline-text-color-light: $middle-gray, hint-inline-text-color: #cfcfcf, hint-item-active-background-color: unset, @@ -272,12 +350,12 @@ $themes: ( hint-item-active-type-text-color: $lighter, hint-item-border-bottom-color: $medium-dark, hint-item-hover-background-color: $dark, - hint-keyword-text-color: #F5DC23, + hint-keyword-text-color: #f5dc23, hint-no-link-background-color: $medium-dark, hint-text-color: $medium-light, hint-type-text-color: $middle-light, hint-var-active-border-bottom-color: none, - hint-var-text-color: #FFA9D9, + hint-var-text-color: #ffa9d9, icon-color: $medium-light, icon-hover-color: $yellow, icon-toast-hover-color: $yellow, @@ -307,9 +385,10 @@ $themes: ( search-hover-background-color: $yellow, search-hover-text-color: $dark, secondary-text-color: $lighter, + selected-background-color: $p5-contrast-selected, shadow-color: rgba(0, 0, 0, 0.16), table-button-active-color: $dark, - table-button-background-active-color: #00FFFF, + table-button-background-active-color: #00ffff, table-button-background-color: $middle-gray, table-button-background-hover-color: $yellow, table-button-color: $dark, @@ -319,6 +398,6 @@ $themes: ( toast-background-color: $medium-light, toast-text-color: $darker, toolbar-button-background-color: $medium-light, - toolbar-button-color: $dark, + toolbar-button-color: $dark ) ); diff --git a/client/styles/components/_collection.scss b/client/styles/components/_collection.scss index bc5f9db5db..412553e93d 100644 --- a/client/styles/components/_collection.scss +++ b/client/styles/components/_collection.scss @@ -183,3 +183,44 @@ } } } + +.pagination { + width: 100%; + position: absolute; + z-index: 1; + bottom: 1.5rem; + left: 0; + + .pagination-ul { + bottom: 0; + display: flex; + justify-content: center; + + > li { + margin: 0 0.2rem; + + .page-link { + font-weight: 800; + padding: 0 1rem; + } + } + + .bold-text { + font-weight: bold; + } + } + + .page-link:disabled, + .page-item.disabled .page-link { + cursor: not-allowed; + opacity: 0.5; + } +} + +.pagination-overlay { + bottom: 1rem; +} + +.pagination-ul > li button:hover { + text-decoration: underline; +} diff --git a/client/styles/components/_editor.scss b/client/styles/components/_editor.scss index 0aab1d4b9f..93866036cd 100644 --- a/client/styles/components/_editor.scss +++ b/client/styles/components/_editor.scss @@ -1,386 +1,168 @@ @use "sass:math"; -.CodeMirror { +.cm-editor { font-family: Inconsolata, monospace; height: 100%; -} - -.CodeMirror-linenumbers { - padding-right: #{math.div(10, $base-font-size)}rem; -} -.CodeMirror-linenumber { - width: #{math.div(32, $base-font-size)}rem; - left: #{math.div(-3, $base-font-size)}rem !important; @include themify() { - color: getThemifyVariable("inactive-text-color"); + color: getThemifyVariable('primary-text-color'); } -} - -.CodeMirror-lines { - padding-top: #{math.div(25, $base-font-size)}rem; -} - -pre.CodeMirror-line { - padding-left: #{math.div(5, $base-font-size)}rem; -} - -.CodeMirror-gutter-wrapper { - right: 100%; - top: 0; - bottom: 0; -} - -.CodeMirror-lint-marker-warning, -.CodeMirror-lint-marker-error, -.CodeMirror-lint-marker-multiple { - background-image: none; - width: #{math.div(49, $base-font-size)}rem; - position: absolute; - height: 100%; - right: 100%; -} - -.CodeMirror-lint-message-error, -.CodeMirror-lint-message-warning { - background-image: none; - padding-left: inherit; -} - -.CodeMirror-lint-marker-warning { - background-color: rgb(255, 190, 5); -} -.CodeMirror-lint-marker-error { - background-color: rgb(255, 95, 82); -} + .cm-content { + padding-top: #{math.div(25, $base-font-size)}rem; + + .cm-activeLine { + // Needed to place the active line highlight behind the selection highlight. + position: relative; + background-color: transparent; + + &::before { + content: ''; + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + z-index: -10; + + @include themify() { + background-color: getThemifyVariable('activeline-background-color'); + } + } + } -.CodeMirror-gutter-elt:not(.CodeMirror-linenumber) { - opacity: 0.2; - width: #{math.div(49, $base-font-size)}rem !important; - height: 100%; - left: 49px !important; - // background-color: rgb(255, 95, 82); -} + .cm-errorLine.cm-activeLine, + .cm-errorLine { + background-color: rgb(255 95 82 / 30%); + } + } + .cm-focused > .cm-scroller .cm-selectionLayer .cm-selectionBackground, + .cm-selectionLayer .cm-selectionBackground, + ::selection { + @include themify() { + background-color: getThemifyVariable( + 'selected-background-color' + ) !important; + } + } -.CodeMirror-lint-tooltip { - @include themify() { - background-color: getThemifyVariable("modal-background-color"); - border: 1px solid getThemifyVariable("modal-border-color"); - box-shadow: 0 12px 12px getThemifyVariable("shadow-color"); - color: getThemifyVariable("primary-text-color"); + .cm-cursorLayer .cm-cursor { + @include themify() { + border-left-color: getThemifyVariable('primary-text-color'); + } } - border-radius: 2px; - font-family: Montserrat, sans-serif; } -.CodeMirror-gutters { +.cm-gutters { @include themify() { - background-color: getThemifyVariable("editor-gutter-color"); - border-color: getThemifyVariable("ide-border-color"); + background-color: getThemifyVariable('editor-gutter-color'); + border-color: getThemifyVariable('ide-border-color'); } - // left: 0 !important; width: #{math.div(48, $base-font-size)}rem; -} - -/* - Search dialog -*/ + position: relative; -.CodeMirror-dialog { - position: fixed; - top: 0; - left: 50%; - margin-left: #{math.div(-552 * 0.5, $base-font-size)}rem; - - @media (max-width: 770px) { - left: 0; - right: 0; - width: 100%; - margin-left: 0; + .cm-gutter { + z-index: 1; } - z-index: 10; - - width: 580px; - font-family: Montserrat, sans-serif; - - padding: #{math.div(8, $base-font-size)}rem #{math.div(10, $base-font-size)}rem #{math.div(5, $base-font-size)}rem #{math.div(9, $base-font-size)}rem; - - border-radius: 2px; - - @include themify() { - background-color: getThemifyVariable("modal-background-color"); - box-shadow: 0 12px 12px 0 getThemifyVariable("shadow-color"); - border: solid 0.5px getThemifyVariable("modal-border-color"); + .cm-lineNumbers { + @include themify() { + color: getThemifyVariable('inactive-text-color'); + } + pointer-events: none; } -} -.CodeMirror-find-popup-container { - display: flex; - flex-wrap: wrap; - justify-content: space-between; -} - -.Toggle-replace-btn-div { - height: #{math.div(40, $base-font-size)}rem; - padding: 0; -} - -.Toggle-replace-btn-div > button { - width: 100%; - height: 100%; -} - -.CodeMirror-search-results { - margin: 0 #{math.div(20, $base-font-size)}rem; - width: #{math.div(75, $base-font-size)}rem; - font-size: #{math.div(12, $base-font-size)}rem; -} - -.CodeMirror-find-controls { - width: 100%; - display: flex; - align-items: center; - justify-content: space-between; - height: #{math.div(35, $base-font-size)}rem; -} -.CodeMirror-search-inputs { - width: 30%; - margin-left: 10px; -} -.CodeMirror-replace-div { - display: flex; - justify-content: flex-start; - align-items: center; -} -.CodeMirror-search-controls { - width: 60%; - display: flex; - flex-wrap: wrap-reverse; - justify-content: flex-start; - align-items: flex-end; -} -.CodeMirror-replace-controls { - display: flex; - margin-left: #{math.div(10, $base-font-size)}rem; -} - -.CodeMirror-replace-options { - width: #{math.div(552, $base-font-size)}rem; - height: #{math.div(65, $base-font-size)}rem; - display: flex; - justify-content: center; - align-items: center; -} -.CodeMirror-replace-options button { - width: #{math.div(200, $base-font-size)}rem; -} + .cm-activeLineGutter { + background-color: transparent; + } -.CodeMirror-search-title { - display: block; - margin-bottom: #{math.div(12, $base-font-size)}rem; + .cm-gutter-lint { + position: absolute; + left: 0; + width: 100%; + z-index: 0; - font-size: #{math.div(21, $base-font-size)}rem; - font-weight: bold; -} + .cm-activeLineGutter { + @include themify() { + background-color: getThemifyVariable('active-line-background-color'); + } + } -.CodeMirror-search-field { - display: block; - width: 100%; - max-width: #{math.div(166, $base-font-size)}rem; - margin-bottom: #{math.div(4, $base-font-size)}rem; - @include themify() { - color: getThemifyVariable("input-text-color"); - background-color: getThemifyVariable("input-secondary-background-color"); - border: solid 0.5px getThemifyVariable("button-border-color"); - &::placeholder { - color: getThemifyVariable("inactive-text-color"); + .cm-gutterElement { + padding: 0; } } -} - -.CodeMirror-search-nav { - display: flex; - align-items: center; -} - -.CodeMirror-search-count { - display: block; - height: #{math.div(20, $base-font-size)}rem; - text-align: right; -} - -.CodeMirror-search-actions { - display: flex; - justify-content: space-between; -} - -.CodeMirror-search-modifiers { - display: flex; - justify-content: flex-end; - align-items: center; - margin-left: #{math.div(10, $base-font-size)}rem; - @media (max-width: 579px) { - display: none; - } -} + .cm-lint-marker { + content: ''; + width: 100%; + height: 100%; + opacity: 0.2; -.CodeMirror-regexp-button, -.CodeMirror-case-button, -.CodeMirror-word-button { - @include themify() { - // @extend %button; - padding: #{math.div(2, $base-font-size)}rem #{math.div(7, $base-font-size)}rem; - border: 2px solid transparent; - &:hover { - border-color: getThemifyVariable("button-border-color"); + &.cm-lint-marker-error { + background-color: rgb(255, 95, 82); } - } - width: #{math.div(35, $base-font-size)}rem; - height: #{math.div(35, $base-font-size)}rem; - & + & { - margin-left: #{math.div(3, $base-font-size)}rem; + &.cm-lint-marker-warning { + background-color: rgb(255, 190, 5); + } } - - word-break: keep-all; - white-space: nowrap; -} - -.CodeMirror-regexp-button .label, -.CodeMirror-case-button .label, -.CodeMirror-word-button .label { - @extend %hidden-element; } -[aria-checked="true"] { +.cm-tooltip-lint { @include themify() { - color: getThemifyVariable("heavy-text-color"); - background-color: getThemifyVariable("button-secondary-background-color"); - border-color: getThemifyVariable("button-border-color"); + background-color: getThemifyVariable('modal-background-color'); + border: 1px solid getThemifyVariable('modal-border-color'); + box-shadow: 0 12px 12px getThemifyVariable('shadow-color'); + color: getThemifyVariable('primary-text-color'); } + border-radius: 2px; + font-family: Montserrat, sans-serif; } -/* - Previous / Next buttons -*/ - -// Visually hide button text -.CodeMirror-search-button .label { - @extend %hidden-element; -} - -.CodeMirror-search-button { - margin-right: #{math.div(10, $base-font-size)}rem; -} - -.CodeMirror-search-match { - background: gold; - border-top: #{math.div(1, $base-font-size)}rem solid orange; - border-bottom: #{math.div(1, $base-font-size)}rem solid orange; - box-sizing: border-box; - opacity: 0.5; -} - -/* - Close button -*/ -.CodeMirror-close-button-container { - display: flex; - align-items: center; -} - -// foldgutter -.CodeMirror-foldmarker { - text-shadow: - -1px 0 #ed225d, - 0 1px #ed225d, - 1px 0 #ed225d, - 0 -1px #ed225d; - color: #fff; - /* background-color: rgba(237, 34, 93, 0.42); */ - /* border-radius: 3px; */ - font-weight: bold; - font-family: arial; - line-height: 0.3; - cursor: pointer; - opacity: 0.75; -} -.CodeMirror-foldgutter { - width: 2.7em; -} -.CodeMirror-foldgutter-open, -.CodeMirror-foldgutter-folded { - cursor: pointer; - padding-bottom: 0.4em; - text-align: right; - line-height: 1; -} -.CodeMirror-foldgutter-open:after { - content: "\25BE"; -} -.CodeMirror-foldgutter-folded:after { - content: "\25B8"; -} - -.CodeMirror-foldgutter-open, -.CodeMirror-foldgutter-folded { - position: absolute; - right: 100%; -} - -.CodeMirror-foldgutter-open:after { +/** Search dialog */ +.cm-search.cm-panel { @include themify() { - background-image: getThemifyVariable("codefold-icon-open"); + color: getThemifyVariable('primary-text-color'); + background-color: getThemifyVariable('modal-background-color'); + box-shadow: 0 12px 12px 0 getThemifyVariable('shadow-color'); + border: solid 0.5px getThemifyVariable('modal-border-color'); } -} + font-family: Montserrat, sans-serif; -.CodeMirror-foldgutter-folded:after { - @include themify() { - background-image: getThemifyVariable("codefold-icon-closed"); + .cm-button { + @include themify() { + @extend %link; + color: getThemifyVariable('secondary-text-color'); + &:hover { + color: getThemifyVariable('logo-color'); + } + } + background: transparent; + border: none; + font-size: 1rem; } -} - -.CodeMirror-foldgutter-folded:after, -.CodeMirror-foldgutter-open:after { - background-size: 10px 10px; - content: ""; - padding-left: 15px; - background-repeat: no-repeat; - background-position: center center; -} -.CodeMirror-foldmarker { - text-shadow: none; - border-radius: 5px; - opacity: 1; - font-weight: normal; - display: inline-block; - vertical-align: middle; - height: 0.85em; - line-height: 0.7; - padding: 0 #{math.div(5, $base-font-size)}rem; - font-family: serif; -} - -.line-runtime-error + .CodeMirror-activeline-gutter { - background-color: rgb(255, 95, 82); - opacity: 0.3; + input.cm-textfield { + @include themify() { + color: getThemifyVariable('input-text-color'); + background-color: getThemifyVariable('input-secondary-background-color'); + border: solid 0.5px getThemifyVariable('button-border-color'); + &::placeholder { + color: getThemifyVariable('inactive-text-color'); + } + } + } } -.line-runtime-error { - background-color: rgb(255, 95, 82) !important; - opacity: 0.3; -} +// TODO: Add fold gutter styling back. .editor-holder { height: calc(100% - #{math.div(29, $base-font-size)}rem); width: 100%; position: absolute; @include themify() { - border: 1px solid getThemifyVariable("ide-border-color"); + border: 1px solid getThemifyVariable('ide-border-color'); } &.editor-holder--hidden .CodeMirror { display: none; @@ -393,7 +175,7 @@ pre.CodeMirror-line { .editor__file-name { @include themify() { - color: getThemifyVariable("primary-text-color"); + color: getThemifyVariable('primary-text-color'); } height: #{math.div(29, $base-font-size)}rem; padding-top: #{math.div(7, $base-font-size)}rem; @@ -406,7 +188,7 @@ pre.CodeMirror-line { .editor__library-version { @include themify() { - color: getThemifyVariable("primary-text-color"); + color: getThemifyVariable('primary-text-color'); } position: absolute; top: 0; @@ -422,41 +204,125 @@ pre.CodeMirror-line { } /** Inline abbreviation preview */ - -.emmet-abbreviation-preview { +.emmet-preview.cm-tooltip { @extend %modal; - position: absolute; @include themify() { - background: getThemifyVariable("background-color"); + background: getThemifyVariable('background-color'); } - & .CodeMirror-lines { + .cm-content { padding: 0; } - & .CodeMirror { - height: auto; - max-width: #{math.div(400, $base-font-size)}rem; - max-height: #{math.div(300, $base-font-size)}rem; - border: none; +} + +// Used for comment, lineComment, blockComment, docComment, docString +.cm-comment { + @include themify() { + color: getThemifyVariable('highlight-style-comment-color'); + } +} + +// Used for name, variableName, typeName +.cm-variable { + @include themify() { + color: getThemifyVariable('highlight-style-variable-color'); + } +} + +// Used for string, character, attributeValue +.cm-string { + @include themify() { + color: getThemifyVariable('highlight-style-string-color'); + } +} + +// Used for regexp +.cm-regexp { + @include themify() { + color: getThemifyVariable('highlight-style-regexp-color'); + } +} + +// Used for number, integer, float +.cm-number { + @include themify() { + color: getThemifyVariable('highlight-style-number-color'); + } +} + +// Used for bool, atom, null +.cm-atom { + @include themify() { + color: getThemifyVariable('highlight-style-atom-color'); } } -.emmet-abbreviation-preview:not(.has-error) .emmet-abbreviation-preview-error { - display: none; +// Used for keyword, self, function, className +.cm-keyword { + @include themify() { + color: getThemifyVariable('highlight-style-keyword-color'); + } } -.emmet-abbreviation-preview.has-error .CodeMirror { - display: none; +// Used for operatorKeyword, controlKeyword, operator, derefOperator, arithmeticOperator, +// logicOperator, bitwiseOperator, compareOperator, updateOperator, typeOperator +// controlOperator +.cm-operator { + @include themify() { + color: getThemifyVariable('highlight-style-operator-color'); + } } -.emmet-abbreviation-preview .CodeMirror-cursors { - visibility: hidden !important; +// Used for definitionKeyword, definition, const, local +.cm-def { + @include themify() { + color: getThemifyVariable('highlight-style-def-color'); + } } -.emmet-abbreviation-preview .emmet-error-snippet-message { - padding: 5px; +// tagName, heading, heading1, heading2, heading3, heading4, heading5, heading6, +// list, quote, emphasis, strong, link +.cm-tag { + @include themify() { + color: getThemifyVariable('highlight-style-tag-color'); + } } -.emmet-open-tag, -.emmet-close-tag { - text-decoration: underline; +// Used for propertyName +.cm-property { + @include themify() { + color: getThemifyVariable('highlight-style-property-color'); + } } + +// Used for attributeName +.cm-attribute { + @include themify() { + color: getThemifyVariable('highlight-style-attribute-color'); + } +} + +// Unassigned Lezer tags: literal, escape, color, url, unit, modifier, punctuation, +// separator, bracket, angleBracket, squareBracket, paren, brace, content +// contentSeparator, monospace, strikethrough, inserted, deleted, changed, invalid, meta, +// documentMeta, annotation, processingInstruction, standard, special, macroName + +// TODO(connie): Add p5 specific highlighting styles, like .cm-p5-function, .cm-p5-variable + +// Additional matching selection styling +.cm-matchingBracket { + @include themify() { + outline: 1px solid getThemifyVariable('primary-text-color'); + outline-offset: 1px; + background-color: transparent !important; + } +} + +.cm-selectionMatch, +.cm-searchMatch, +.cm-searchMatch-selected { + @include themify() { + background-color: getThemifyVariable( + 'highlight-style-matching-selection-background-color' + ); + } +} \ No newline at end of file diff --git a/client/styles/components/_hints.scss b/client/styles/components/_hints.scss index 7084b460e7..2c22413ed0 100644 --- a/client/styles/components/_hints.scss +++ b/client/styles/components/_hints.scss @@ -1,276 +1,250 @@ @use "sass:math"; -.CodeMirror-hints { - position: absolute; - z-index: 10; - overflow: hidden; - list-style: none; - - margin: 0; - padding: 0; - +.cm-tooltip-autocomplete.CodeMirror-hints { box-shadow: 0 0 #{math.div(18, $base-font-size)}rem 0 rgba(0, 0, 0, 0.16); border: #{math.div(1, $base-font-size)}rem solid #A6A6A6; - - font-size: 100%; font-family: Inconsolata, monospace; - - width: 20rem; - max-height: 20rem; - overflow-y: auto; - - transform-origin: top left; + font-size: 1rem; @include themify() { background: getThemifyVariable('hint-background-color'); + color: getThemifyVariable('hint-text-color'); + } - .CodeMirror-hint { - color: getThemifyVariable('hint-text-color'); - border-bottom: #{math.div(1, $base-font-size)}rem solid getThemifyVariable('hint-item-border-bottom-color'); + ul { + @include themify() { + background: getThemifyVariable('hint-background-color'); } + } - .hint-container { - display: flex; - align-items: center; - flex-direction: column; - justify-content: center; - width: 100%; - height: 100%; + li.CodeMirror-hint { + display: grid; + grid-template-columns: minmax(0, 1fr) auto #{math.div(40, $base-font-size)}rem; + align-items: center; + min-height: #{math.div(24, $base-font-size)}rem; + font-weight: 500; - // Widen the entire row only if a warning is present - &.has-warning { - width: 100%; // Let it fill the parent .CodeMirror-hint width - max-width: 24rem; - } + @include themify() { + background: getThemifyVariable('hint-background-color'); + color: getThemifyVariable('hint-text-color'); + border-bottom: #{math.div(1, $base-font-size)}rem solid + getThemifyVariable('hint-item-border-bottom-color'); } - .hint-main { - display: flex; - justify-content: space-between; - align-items: center; - flex-grow: 1; - padding: 0 0.5rem; - width: 100%; - height: 100%; - // position: relative; // optional, only if you want absolutely-positioned children + &:hover:not([aria-selected='true']) { + @include themify() { + background: getThemifyVariable('hint-item-hover-background-color'); + } } - .hint-main a { - display: flex; - align-items: center; - justify-content: center; - width: 2rem; - height: 100%; - padding: 0; - // margin-left: auto; - text-align: center; - text-decoration: none; - font-size: 1.2rem; - } + &[aria-selected='true'] { + @include themify() { + background: getThemifyVariable('hint-item-active-background-color'); + color: getThemifyVariable('hint-item-active-text-color'); + outline: getThemifyVariable('hint-item-active-outline'); + outline-offset: getThemifyVariable('hint-item-active-outline-offset'); + } - .hint-name { - font-size: 1.2rem; - font-weight: bold; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - } + .cm-completionKind { + @include themify() { + color: getThemifyVariable('hint-item-active-type-text-color'); + } + } - .hint-type { - font-size: 1rem; - font-weight: normal; - color: #999; - margin-left: 1rem; - margin-right: 2.5rem; // leaves space for the arrow icon - white-space: nowrap; - flex-shrink: 0; + .cm-completionMatchedText { + color: inherit; + } } - // Warning box - .blacklist-warning { - background-color: #fff3cd; - border: 1px solid #ffc107; - color: #856404; - padding: 6px 10px; - border-radius: 4px; - font-size: 0.85rem; - margin: 0.25rem 0.5rem 0 0.5rem; - width: calc(100% - 1rem); // Match padding - box-sizing: border-box; - } - - .fun-name, .obj-name { - color: getThemifyVariable('hint-fun-text-color'); - } - - .var-name, .boolean-name { - color: getThemifyVariable('hint-var-text-color'); + &.blacklisted { + height: auto; + min-height: 3.2rem; // enough to show the warning + content } + } - .keyword-name { - color: getThemifyVariable('hint-keyword-text-color'); - } - - .hint-type { + .cm-completionLabel { + grid-column: 1; + justify-self: start; + margin-left: #{math.div(12, $base-font-size)}rem; + line-height: 1.1; + font-weight: 600; + color: inherit; + background: transparent; + } + + .cm-completionMatchedText { + color: inherit; + text-decoration: none; + font-weight: inherit; + } + + .cm-completionDetail { + display: none; + } + + .cm-completionKind { + grid-column: 2; + justify-self: end; + padding-right: #{math.div(28, $base-font-size)}rem; + font-size: 0.8rem; + + @include themify() { color: getThemifyVariable('hint-type-text-color'); - margin-right: #{math.div(10, $base-font-size)}rem; } - - a { - color: getThemifyVariable('hint-arrow-color'); + } + + .cm-completionRefLink { + grid-column: 3; + display: flex; + align-items: center; + justify-content: center; + align-self: stretch; + width: 75%; + text-decoration: none; + + @include themify() { background: getThemifyVariable('hint-arrow-background-color'); + color: getThemifyVariable('hint-arrow-color'); + } - &:hover, &:active, &.focused-hint-link { + &:hover, + &:active, + &.focused-hint-link { + @include themify() { background: getThemifyVariable('hint-arrow-background-active-color'); } + } + + &.focused-hint-link { + width: 100%; + height: 100%; + margin: 0; + align-self: stretch; - &.focused-hint-link { - outline: #{math.div(3, $base-font-size)}rem solid getThemifyVariable('hint-arrow-focus-outline-color'); + @include themify() { + outline: #{math.div(3, $base-font-size)}rem solid + getThemifyVariable('hint-arrow-focus-outline-color'); outline-offset: #{math.div(-3, $base-font-size)}rem; } } + } - .no-link-placeholder { + .cm-completionRefLink--disabled, + li:not(.has-doc-link) .cm-completionRefLink { + @include themify() { background: getThemifyVariable('hint-no-link-background-color'); - pointer-events: none; + color: getThemifyVariable('hint-arrow-color'); } - - li.CodeMirror-hint-active:not(.unfocused) { - background: getThemifyVariable('hint-item-active-background-color'); - outline: getThemifyVariable('hint-item-active-outline'); - outline-offset: getThemifyVariable('hint-item-active-outline-offset'); - - // .fun-item { - // border-bottom: #{2 / $base-font-size}rem solid getThemifyVariable('hint-fun-active-border-bottom-color'); - // } - - // .var-item { - // border-bottom: #{2 / $base-font-size}rem solid getThemifyVariable('hint-var-active-border-bottom-color'); - // } + } - .hint-name { - color: getThemifyVariable('hint-item-active-text-color'); + // label colors + li.hint-type-method, + li.hint-type-obj { + .cm-completionLabel, + .cm-completionMatchedText { + @include themify() { + color: getThemifyVariable('hint-fun-text-color'); } + } + } - .fun-name, .obj-name { - background-color: getThemifyVariable('hint-fun-text-color'); - } - - .var-name, .boolean-name { - background-color: getThemifyVariable('hint-var-text-color'); + li.hint-type-variable, + li.hint-type-constant, + li.hint-type-boolean { + .cm-completionLabel, + .cm-completionMatchedText { + @include themify() { + color: getThemifyVariable('hint-var-text-color'); } + } + } - .keyword-name { - background-color: getThemifyVariable('hint-keyword-text-color'); - } - - .hint-type, .plain-hint-item { - color: getThemifyVariable('hint-item-active-type-text-color'); + li.hint-type-keyword { + .cm-completionLabel, + .cm-completionMatchedText { + @include themify() { + color: getThemifyVariable('hint-keyword-text-color'); } } - - .CodeMirror-hint:hover:not(.CodeMirror-hint-active) { - background: getThemifyVariable('hint-item-hover-background-color'); - } } - .CodeMirror-hint { - display: flex; - align-items: center; - justify-content: space-between; - - position: relative; - margin: 0; - padding: 0; - height: 2rem; - white-space: pre; - cursor: pointer; - - &:has(.focused-hint-link) { - z-index: 999; + // highlighted completion label colors + li.hint-type-method[aria-selected='true'], + li.hint-type-obj[aria-selected='true'] { + .cm-completionLabel { + @include themify() { + background: getThemifyVariable('hint-fun-text-color'); + color: getThemifyVariable('hint-item-active-text-color'); + border-bottom: #{math.div(1, $base-font-size)}rem solid + getThemifyVariable('hint-fun-active-border-bottom-color'); + } } + } - &:only-child, &:last-child { - border-bottom: none !important; + li.hint-type-variable[aria-selected='true'], + li.hint-type-constant[aria-selected='true'], + li.hint-type-boolean[aria-selected='true'] { + .cm-completionLabel { + @include themify() { + background: getThemifyVariable('hint-var-text-color'); + color: getThemifyVariable('hint-item-active-text-color'); + border-bottom: #{math.div(1, $base-font-size)}rem solid + getThemifyVariable('hint-var-active-border-bottom-color'); + } } + } - p { - display: flex; - width: 100%; - height: 100%; - } - - .hint-name, .plain-hint-item { - display: flex; - align-items: center; - padding: 0 0.5rem; - width: min-content; - font-size: 1.2rem; - line-height: 100%; - font-weight: bold; - } - - .hint-type { - margin: 0.5rem 2.4rem 0.5rem auto; - font-size: 1rem; - line-height: 100%; - font-weight: normal; + li.hint-type-keyword[aria-selected='true'] { + .cm-completionLabel { + @include themify() { + background: getThemifyVariable('hint-keyword-text-color'); + color: getThemifyVariable('hint-item-active-text-color'); + } } + } - .hint-hidden { - @extend %hidden-element; - } - - a, .no-link-placeholder { - // position: absolute; - top: 0; - right: 0; - height: 100%; - width: calc(2rem - #{math.div(1, $base-font-size)}rem); - margin: 0; - padding-top: 0.4rem; - font-size: 1.2rem; - line-height: 100%; - text-align: center; - outline: none; - z-index: 1; - } - - a:focus, a:active { - outline: 0; - } + .cm-completionWarning { + grid-column: 1 / 4; + display: inline-flex; + align-items: center; + gap: 0.35rem; + width: fit-content; + margin: 0.35rem 0.75rem 0.5rem 0.75rem; + padding: 0.32rem 0.55rem; + border: 1px solid #d9a300; + border-radius: 0.45rem; + background: #f3e7b7; + color: #9a6b00; + font-size: 0.8rem; + line-height: 1.1; + white-space: nowrap; } - .CodeMirror-hint.blacklisted { - height: auto; - min-height: 3.2rem; // enough to show the warning + content + .cm-completionWarningIcon { + flex: 0 0 auto; + font-size: 1rem; } -} -// Inline hinter -.CodeMirror-widget { - line-height: inherit; + .cm-completionWarningText { + line-height: 1.1; + } - @include themify() { - .autocomplete-inline-hinter { - // make the border left look like a cursor and animate like a cursor - // border-left: #{1.2 / $base-font-size}rem solid getThemifyVariable(hint-inline-text-color); - // animation: inline-hint-caret-blink 1s step-end infinite; - pointer-events: none; - - .inline-hinter-suggestion { - color: getThemifyVariable(hint-inline-text-color); - font-style: italic; - } + .cm-ghostCompletion { + @include themify() { + color: getThemifyVariable('hint-inline-text-color'); + } + } - .inline-hinter-suggestion-light { - color: getThemifyVariable(hint-inline-text-color-light); - font-style: italic; - } + .cm-ghostCompletion.cm-ghostCompletion--light { + @include themify() { + color: getThemifyVariable('hint-inline-text-color-light'); } } -} -@keyframes inline-hint-caret-blink { - 50% { border-color: transparent; } + .hint-hidden { + @include themify() { + @extend %hidden-element; + display: none; + } + } } diff --git a/client/styles/components/_p5-contrast-codemirror-theme.scss b/client/styles/components/_p5-contrast-codemirror-theme.scss deleted file mode 100644 index 93c604d0cc..0000000000 --- a/client/styles/components/_p5-contrast-codemirror-theme.scss +++ /dev/null @@ -1,152 +0,0 @@ -// brown: #6C4D13 -// black: #333 -// blue: #0F9DD7 -// pink: #D9328F -// gray: #999999 -// dark blue: #318094 -// white: #fdfdfd - -//numbers -//light gray: #f4f4f4 -//dark gray: #b5b5b5 - -@use "sass:math"; - -$p5-contrast-black: #1C1C1C; -$p5-contrast-gray: #A0A0A0; -$p5-contrast-white: #FDFDFD; -$p5-contrast-darkgray: #333333; -$p5-contrast-lightgray: #C1C1C1; -$p5-contrast-blue: #00FFFF; -$p5-contrast-green: #2DE9B6; -$p5-contrast-yellow: #F5DC23; -$p5-contrast-orange: #FFA95D; -$p5-contrast-pink: #FFA9D9; - -$p5-contrast-gutter: #454545; -$p5-contrast-number: #FDFDFD; -$p5-contrast-selected: $middle-dark; -$p5-contrast-activeline: #999999; - -.cm-s-p5-contrast { - background-color: $p5-contrast-black; - color: $p5-contrast-white; -} - -.cm-s-p5-contrast span .cm-comment { - color: $p5-contrast-lightgray; -} - -.cm-s-p5-contrast span .cm-def { - color: $p5-contrast-blue; -} - -.cm-s-p5-contrast span .cm-string { - color: $p5-contrast-green; -} - -.cm-s-p5-contrast span .cm-string-2 { - color: $p5-contrast-green; -} - -.cm-s-p5-contrast span .cm-number { - color: $p5-contrast-pink; -} - -.cm-s-p5-contrast span .cm-keyword { - color: $p5-contrast-yellow; -} - -.cm-s-p5-contrast span .cm-variable { - color: $p5-contrast-white; -} - -.cm-s-p5-contrast span .cm-variable-2 { - color: $p5-contrast-white; -} - -.cm-s-p5-contrast span .cm-property { - color: $p5-contrast-white; -} - -.cm-s-p5-contrast span .cm-atom { - color: $p5-contrast-pink; -} - -.cm-s-p5-contrast span .cm-operator { - color: $p5-contrast-lightgray; -} - -.cm-s-p5-contrast .cm-linenumber { - color: $p5-contrast-number; -} - -.cm-s-p5-contrast { - .CodeMirror-selected { background: $p5-contrast-selected; } - .CodeMirror-focused .CodeMirror-selected { background: $p5-contrast-selected; } - .CodeMirror-line::selection, .CodeMirror-line > span::selection, .CodeMirror-line > span > span::selection { background: $p5-contrast-selected; } - .CodeMirror-line::-moz-selection, .CodeMirror-line > span::-moz-selection, .CodeMirror-line > span > span::-moz-selection { background: $p5-contrast-selected; } -} - -.cm-s-p5-contrast .CodeMirror-activeline-background { - background-color: $dark; -} - -.cm-s-p5-contrast .CodeMirror-activeline-gutter { - background-color: $dark; - border-right: 1px solid $middle-dark; -} - -.cm-s-p5-contrast .cm-error { - color: #f00; -} - -.cm-s-p5-contrast span .CodeMirror-matchingbracket { - outline: 1px solid $p5-contrast-lightgray; - outline-offset: 1px; - color: $p5-contrast-white !important; -} - -.cm-s-p5-contrast span .cm-qualifier { - color: $p5-contrast-yellow; -} - -.cm-s-p5-contrast span .cm-tag { - color: $p5-contrast-orange; -} - -.cm-s-p5-contrast span .cm-builtin { - color: $p5-contrast-yellow; -} - -.cm-s-p5-contrast span .cm-attribute { - color: $p5-contrast-white; -} - -.cm-s-p5-contrast .cm-p5-function { - color: $p5-contrast-blue; -} - -.cm-s-p5-contrast .cm-p5-variable { - color: $p5-contrast-pink; - font-weight: bold; -} - -.cm-s-p5-contrast .CodeMirror-foldmarker { - background-color: white; - color: #333; -} - -.cm-s-p5-contrast .CodeMirror-cursor { - border-left: 1px solid $p5-contrast-white; -} - -.cm-s-p5-contrast .cm-searching { - // background-color: $p5js-pink-opacity; - background-color: $medium-dark; -} - -.cm-s-p5-contrast .cm-searching.CodeMirror-selectedtext { - // background-color: $medium-dark; - outline: #{math.div(1, $base-font-size)}rem solid $p5-contrast-white; -} diff --git a/client/styles/components/_p5-dark-codemirror-theme.scss b/client/styles/components/_p5-dark-codemirror-theme.scss deleted file mode 100644 index 77940fa188..0000000000 --- a/client/styles/components/_p5-dark-codemirror-theme.scss +++ /dev/null @@ -1,151 +0,0 @@ -// brown: #6C4D13 -// black: #333 -// blue: #0F9DD7 -// pink: #D9328F -// gray: #999999 -// dark blue: #318094 -// white: #fdfdfd - -//numbers -//light gray: #f4f4f4 -//dark gray: #b5b5b5 - -$p5-dark-lightbrown: #A67F59; -$p5-light-green: #42F48F; -$p5-dark-black: #1C1C1C; -$p5-dark-pink: #DE4A9B; -$p5-dark-gray: #9B9B9B; -$p5-dark-lightblue: #0F9DD7; -$p5-dark-darkblue: #318094; -$p5-dark-white: #FDFDFD; -$p5-dark-orange: #EE9900; -$p5-dark-lightgray: #E0D7D1; -$p5-dark-darkgray: #666666; -$p5-dark-green: #58a10b; -$p5-dark-goldbrown: #b58318; - -$p5-dark-gutter: #f4f4f4; -$p5-dark-number: #b5b5b5; -$p5-dark-selected: $medium-dark; -$p5-dark-activeline: rgb(207, 207, 207); - -$p5-dark-error: #df3a3d; - -.cm-s-p5-dark { - background-color: $p5-dark-black; - color: $p5-dark-white; -} - -.cm-s-p5-dark span.cm-comment { - color: $p5-dark-gray; -} - -.cm-s-p5-dark span.cm-def { - color: $p5-dark-lightblue; -} - -.cm-s-p5-dark span.cm-string { - color: $p5-dark-green; -} - -.cm-s-p5-dark span.cm-string-2 { - color: $p5-dark-orange; -} - -.cm-s-p5-dark span.cm-number { - color: $p5-dark-white; -} - -.cm-s-p5-dark span.cm-keyword { - color: $p5-dark-goldbrown; -} - -.cm-s-p5-dark span.cm-variable { - color: $p5-dark-lightblue; -} - -.cm-s-p5-dark span.cm-variable-2 { - color: $p5-dark-white; -} - -.cm-s-p5-dark span.cm-property { - color: $p5-dark-white; -} - -.cm-s-p5-dark span.cm-atom { - color: $p5-dark-pink; -} - -.cm-s-p5-dark span.cm-operator { - color: $p5-dark-white; -} - -.cm-s-p5-dark .cm-linenumber { - color: $p5-dark-number; -} - -.cm-s-p5-dark { - .CodeMirror-selected { background: $p5-dark-selected; } - .CodeMirror-focused .CodeMirror-selected { background: $p5-dark-selected; } - .CodeMirror-line::selection, .CodeMirror-line > span::selection, .CodeMirror-line > span > span::selection { background: $p5-dark-selected; } - .CodeMirror-line::-moz-selection, .CodeMirror-line > span::-moz-selection, .CodeMirror-line > span > span::-moz-selection { background: $p5-dark-selected; } -} - -.cm-s-p5-dark .CodeMirror-activeline-background { - background-color: $dark; -} - -.cm-s-p5-dark .CodeMirror-activeline-gutter { - background-color: $dark; - border-right: 1px solid $middle-dark; -} - -.cm-s-p5-dark span.CodeMirror-matchingbracket { - outline: 1px solid $p5-dark-gray; - outline-offset: 1px; - color: $p5-dark-white !important; -} - -.cm-s-p5-dark span.cm-qualifier { - color: $p5-dark-lightblue; -} - -.cm-s-p5-dark span.cm-tag { - color: $p5-dark-pink; -} - -.cm-s-p5-dark span.cm-error { - color: $p5-dark-error; -} - -.cm-s-p5-dark span.cm-builtin { - color: $p5-dark-lightblue; - font-weight: bold; -} - -.cm-s-p5-dark span.cm-attribute { - color: $p5-dark-lightblue; -} - -.cm-s-p5-dark .cm-p5-function { - color: $p5-dark-lightblue; - font-weight: bold !important; -} - -.cm-s-p5-dark .cm-p5-variable { - color: $p5-dark-pink; - font-weight: bold; -} - -.cm-s-p5-dark .CodeMirror-foldmarker { - background-color: white; - color: #333; -} - -.cm-s-p5-dark .CodeMirror-cursor { - border-left: 1px solid $p5-dark-white; -} - -.cm-s-p5-dark .cm-searching { - background-color: $p5js-pink-opacity; -} diff --git a/client/styles/components/_p5-light-codemirror-theme.scss b/client/styles/components/_p5-light-codemirror-theme.scss deleted file mode 100644 index c2e8062865..0000000000 --- a/client/styles/components/_p5-light-codemirror-theme.scss +++ /dev/null @@ -1,144 +0,0 @@ -// brown: #6C4D13 -// black: #333 -// blue: #0F9DD7 -// pink: #D9328F -// gray: #999999 -// dark blue: #318094 -// white: #fdfdfd - -//numbers -//light gray: #f4f4f4 -//dark gray: #b5b5b5 - -$p5-light-brown: #7A5A3A; -$p5-light-black: #333333; -$p5-light-pink: #D52889; -$p5-light-gray: #666; -$p5-light-blue: #0B7CA9; -$p5-light-white: $lighter; -$p5-light-orange: #A06801; -$p5-light-lightgray: $middle-gray; -$p5-light-green: #47820A; - - -$p5-light-gutter: #f4f4f4; -$p5-light-number: #b5b5b5; -$p5-light-selected: $medium-light; -$p5-light-activeline: rgb(207, 207, 207); - -.cm-s-p5-light { - background-color: $p5-light-white; - color: $p5-light-black; -} - -.cm-s-p5-light span .cm-comment { - color: $p5-light-lightgray; -} - -.cm-s-p5-light span .cm-def { - color: $p5-light-blue; -} - -.cm-s-p5-light span .cm-string { - color: $p5-light-green; -} - -.cm-s-p5-light span .cm-string-2 { - color: $p5-light-orange; -} - -.cm-s-p5-light span .cm-number { - color: $p5-light-black; -} - -.cm-s-p5-light .cm-keyword { - color: $p5-light-brown; -} - -.cm-s-p5-light span .cm-variable { - color: $p5-light-blue; -} - -.cm-s-p5-light span .cm-variable2 { - color: $p5-light-black; -} - -.cm-s-p5-light span .cm-property { - color: $p5-light-black; -} - -.cm-s-p5-light span .cm-atom { - color: $p5-light-pink; -} - -.cm-s-p5-light span .cm-operator { - color: $p5-light-brown; -} - -.cm-s-p5-light .cm-linenumber { - color: $p5-light-number; -} - -.cm-s-p5-light { - .CodeMirror-selected { background: $p5-light-selected; } - .CodeMirror-focused .CodeMirror-selected { background: $p5-light-selected; } - .CodeMirror-line::selection, .CodeMirror-line > span::selection, .CodeMirror-line > span > span::selection { background: $p5-light-selected; } - .CodeMirror-line::-moz-selection, .CodeMirror-line > span::-moz-selection, .CodeMirror-line > span > span::-moz-selection { background: $p5-light-selected; } -} - -.cm-s-p5-light .CodeMirror-activeline-background { - background-color: $light; -} - -.cm-s-p5-light .CodeMirror-activeline-gutter { - background-color: $light; - border-right: 1px solid $medium-light; -} - -.cm-s-p5-light .cm-error { - color: #f00; -} - -.cm-s-p5-light span .CodeMirror-matchingbracket { - outline: 1px solid $p5-light-gray; - outline-offset: 1px; - color: $p5-light-black !important; -} - -.cm-s-p5-light span .cm-qualifier { - color: $p5-light-blue; -} - -.cm-s-p5-light span .cm-tag { - color: $p5-light-pink; -} - -.cm-s-p5-light span .cm-builtin { - color: $p5-light-blue; -} - -.cm-s-p5-light span .cm-attribute { - color: $p5-light-black; -} - -.cm-s-p5-light .cm-p5-function { - color: $p5-light-blue; - font-weight: bold; -} - -.cm-s-p5-light .cm-p5-variable { - color: $p5-light-pink; -} - -.cm-s-p5-light .CodeMirror-foldmarker { - background-color: #333; - color: white; -} - -.cm-s-p5-light .CodeMirror-cursor { - border-left: 1px solid $p5-light-black; -} - -.cm-s-p5-light .cm-searching { - background-color: $p5js-pink-opacity; -} diff --git a/client/styles/main.scss b/client/styles/main.scss index 400da75bfb..3d1a0cbe07 100644 --- a/client/styles/main.scss +++ b/client/styles/main.scss @@ -6,15 +6,9 @@ @import 'base/reset'; @import 'base/base'; -@import '~codemirror/lib/codemirror'; -@import '~codemirror/addon/lint/lint'; -@import '~codemirror-colorpicker/addon/codemirror-colorpicker'; @import '~dropzone/dist/dropzone'; @import '~primer-tooltips/build/build'; -@import 'components/p5-light-codemirror-theme'; -@import 'components/p5-dark-codemirror-theme'; -@import 'components/p5-contrast-codemirror-theme'; @import 'components/account'; @import 'components/api-key'; @import 'components/editor'; diff --git a/client/testData/testReduxStore.ts b/client/testData/testReduxStore.ts index 0a150ea2ef..4edbc0c6e4 100644 --- a/client/testData/testReduxStore.ts +++ b/client/testData/testReduxStore.ts @@ -46,7 +46,6 @@ const initialTestState: RootState = { justOpenedProject: false, previousPath: '/', errorType: undefined, - runtimeErrorWarningVisible: true, parentId: undefined }, files: initialFilesState(), @@ -92,8 +91,14 @@ const initialTestState: RootState = { list: [], totalSize: 0 }, - loading: false, - collections: [], +// <<<<<<< HEAD + loading: false, + collections: [], + collectionsListCollections: { + collections: [], +// ======= +}, + // loading: false, collectionsListProjects: { projects: [], metadata: { @@ -104,6 +109,5 @@ const initialTestState: RootState = { hasPagination: true } } -}; - +} export { mockProjects, initialTestState }; diff --git a/client/utils/codemirror-search.js b/client/utils/codemirror-search.js deleted file mode 100644 index 3ed3116a63..0000000000 --- a/client/utils/codemirror-search.js +++ /dev/null @@ -1,830 +0,0 @@ -/* eslint-disable */ -// CodeMirror, copyright (c) by Marijn Haverbeke and others -// Distributed under an MIT license: http://codemirror.net/LICENSE - -// Define search commands. Depends on dialog.js or another -// implementation of the openDialog method. - -// Replace works a little oddly -- it will do the replace on the next -// Ctrl-G (or whatever is bound to findNext) press. You prevent a -// replace by making sure the match is no longer selected when hitting -// Ctrl-G. -import i18n from '../i18n'; -import CodeMirror from 'codemirror'; -import triangleArrowRight from '../images/triangle-arrow-right.svg?byContent'; -import triangleArrowDown from '../images/triangle-arrow-down.svg?byContent'; -import downArrow from '../images/down-arrow.svg?byContent'; -import upArrow from '../images/up-arrow.svg?byContent'; -import exitIcon from '../images/exit.svg?byContent'; - -function searchOverlay(query, caseInsensitive) { - if (typeof query == 'string') { - query = new RegExp( - query.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, '\\$&'), - caseInsensitive ? 'gi' : 'g' - ); - } else if (!query.global) { - query = new RegExp(query.source, query.ignoreCase ? 'gi' : 'g'); - } - - return { - token: function (stream) { - query.lastIndex = stream.pos; - var match = query.exec(stream.string); - if (match && match.index == stream.pos) { - stream.pos += match[0].length || 1; - return 'searching'; - } else if (match) { - stream.pos = match.index; - } else { - stream.skipToEnd(); - } - } - }; -} - -function SearchState() { - this.posFrom = this.posTo = this.lastQuery = this.query = null; - this.overlay = null; - this.regexp = false; - this.caseInsensitive = true; - this.wholeWord = false; - this.replaceStarted = false; - this.lastFileName = - document.querySelector('.editor__file-name span')?.innerText || null; -} - -function getSearchState(cm) { - return cm.state.search || (cm.state.search = new SearchState()); -} - -function getSearchCursor(cm, query, pos) { - // Heuristic: if the query string is all lowercase, do a case insensitive search. - return cm.getSearchCursor(query, pos, getSearchState(cm).caseInsensitive); -} - -function watchFileChanges(cm, searchState, searchField) { - let observer = null; - - function setupObserver() { - var fileNameElement = document.querySelector('.editor__file-name span'); - - if (!fileNameElement) { - setTimeout(setupObserver, 500); - return; - } - - if (observer) { - return; - } - - observer = new MutationObserver(() => { - if (searchField.value.length > 1) { - startSearch(cm, searchState, searchField.value); - } - }); - - observer.observe(fileNameElement, { characterData: true, subtree: true }); - } - - function disconnectObserver() { - if (observer) { - observer.disconnect(); - observer = null; - } - } - - setupObserver(); - - setInterval(() => { - var searchDialog = document.querySelector('.CodeMirror-dialog'); - if (!searchDialog && observer) { - disconnectObserver(); - return; - } else if (searchDialog && !observer) { - setupObserver(); - } - }, 500); -} - -function isMouseClick(event) { - if (event.detail > 0) return true; - else return false; -} - -function persistentDialog(cm, text, deflt, onEnter, replaceOpened, onKeyDown) { - var searchField = document.getElementsByClassName( - 'CodeMirror-search-field' - )[0]; - if (!searchField) { - cm.openDialog(text, onEnter, { - value: deflt, - selectValueOnOpen: true, - closeOnEnter: false, - onClose: function () { - clearSearch(cm); - }, - onKeyDown: onKeyDown, - closeOnBlur: false - }); - - searchField = document.getElementById('Find-input-field'); - - var dialog = document.getElementsByClassName('CodeMirror-dialog')[0]; - var closeButton = dialog.getElementsByClassName('close')[0]; - - var state = getSearchState(cm); - - watchFileChanges(cm, getSearchState(cm), searchField); - - CodeMirror.on(searchField, 'keyup', function (e) { - state.replaceStarted = false; - if (e.keyCode !== 13 && searchField.value.length > 1) { - startSearch(cm, getSearchState(cm), searchField.value); - } else if (searchField.value.length < 1) { - cm.display.wrapper.querySelector( - '.CodeMirror-search-results' - ).innerText = i18n.t('CodemirrorFindAndReplace.NoResults'); - } - }); - - CodeMirror.on(closeButton, 'click', function () { - dialog.parentNode.removeChild(dialog); - clearSearch(cm); - cm.focus(); - }); - - var upArrow = dialog.getElementsByClassName('up-arrow')[0]; - CodeMirror.on(upArrow, 'click', function () { - if (searchField.value.trim() === '') { - searchField.focus(); - } else { - cm.focus(); - CodeMirror.commands.findPrev(cm); - searchField.blur(); - } - }); - - var downArrow = dialog.getElementsByClassName('down-arrow')[0]; - CodeMirror.on(downArrow, 'click', function () { - if (searchField.value.trim() === '') { - searchField.focus(); - } else { - cm.focus(); - CodeMirror.commands.findNext(cm); - searchField.blur(); - } - }); - - var regexpButton = dialog.getElementsByClassName( - 'CodeMirror-regexp-button' - )[0]; - CodeMirror.on(regexpButton, 'click', function (event) { - var state = getSearchState(cm); - state.regexp = toggle(regexpButton); - startSearch(cm, getSearchState(cm), searchField.value); - if (isMouseClick(event)) searchField.focus(); - }); - - toggle(regexpButton, state.regexp); - - var caseSensitiveButton = dialog.getElementsByClassName( - 'CodeMirror-case-button' - )[0]; - CodeMirror.on(caseSensitiveButton, 'click', function (event) { - var state = getSearchState(cm); - state.caseInsensitive = !toggle(caseSensitiveButton); - startSearch(cm, getSearchState(cm), searchField.value); - if (isMouseClick(event)) searchField.focus(); - }); - - toggle(caseSensitiveButton, !state.caseInsensitive); - - var wholeWordButton = dialog.getElementsByClassName( - 'CodeMirror-word-button' - )[0]; - CodeMirror.on(wholeWordButton, 'click', function (event) { - var state = getSearchState(cm); - state.wholeWord = toggle(wholeWordButton); - startSearch(cm, getSearchState(cm), searchField.value); - if (isMouseClick(event)) searchField.focus(); - }); - - toggle(wholeWordButton, state.wholeWord); - - function toggle(el, initialState) { - var currentState, nextState; - - if (initialState == null) { - currentState = el.getAttribute('aria-checked') === 'true'; - nextState = !currentState; - } else { - nextState = initialState; - } - - el.setAttribute('aria-checked', nextState); - return nextState; - } - - function toggleReplace(open) { - var toggleButtonHeightOpened = '80px', - toggleButtonHeightClosed = '40px'; - - if (open) { - replaceFieldDiv.style.display = replaceControlsDiv.style.display = ''; - toggleReplaceBtnDiv.style.height = toggleButtonHeightOpened; - toggleReplaceBtn.style.height = toggleButtonHeightOpened; - toggleReplaceBtn.innerHTML = triangleArrowDown; - } else { - replaceFieldDiv.style.display = replaceControlsDiv.style.display = - 'none'; - toggleReplaceBtnDiv.style.height = toggleButtonHeightClosed; - toggleReplaceBtn.style.height = toggleButtonHeightClosed; - toggleReplaceBtn.innerHTML = triangleArrowRight; - } - } - - var toggleReplaceBtnDiv = document.getElementById('Btn-Toggle-replace-div'); - var toggleReplaceBtn = document.getElementById('Btn-Toggle-replace'); - var replaceFieldDiv = document.getElementById('Replace-input-div'); - var replaceControlsDiv = document.getElementById('Replace-controls-div'); - if (replaceOpened) { - toggleReplace(true); - } - CodeMirror.on(toggleReplaceBtn, 'click', function () { - if (replaceFieldDiv.style.display === 'none') { - toggleReplace(true); - } else { - toggleReplace(false); - } - }); - - var replaceField = document.getElementById('Replace-input-field'); - CodeMirror.on(replaceField, 'keyup', function (e) { - var state = getSearchState(cm); - var query = parseQuery(searchField.value, state); - var withText = parseString(replaceField.value); - if (e.keyCode === 13) { - // if enter - var cursor = getSearchCursor(cm, query, cm.getCursor('from')); - var start = cursor.from(); - var match = cursor.findNext(); - if (!match) { - cursor = getSearchCursor(cm, query); - if ( - !(match = cursor.findNext()) || - (start && - cursor.from().line == start.line && - cursor.from().ch == start.ch) - ) - return; - } - cm.setSelection(cursor.from(), cursor.to()); - state.replaceStarted = true; - doReplace(match, cursor, query, withText); - } - }); - - function doReplace(match, cursor, query, withText) { - cursor.replace( - typeof query == 'string' - ? withText - : withText.replace(/\$(\d)/g, function (_, i) { - return match[i]; - }) - ); - cursor.findNext(); - // cm.focus(); - CodeMirror.commands.findNext(cm); - // searchField.blur(); - } - - var doReplaceButton = document.getElementById('Btn-replace'); - CodeMirror.on(doReplaceButton, 'click', function (e) { - if (!searchField.value) { - searchField.focus(); - return; - } - var state = getSearchState(cm); - var query = parseQuery(searchField.value, state); - var withText = parseString(replaceField.value); - if (state.replaceStarted) { - var cursor = getSearchCursor(cm, query, cm.getCursor('from')); - var match = cursor.findNext(); - if (match) { - cm.setSelection(cursor.from(), cursor.to()); - doReplace(match, cursor, query, withText); - doReplaceButton.focus(); - } - } else { - startSearch(cm, state, searchField.value); - state.replaceStarted = true; - cm.focus(); - CodeMirror.commands.findNext(cm); - searchField.blur(); - doReplaceButton.focus(); - } - }); - - var doReplaceAllButton = document.getElementById('Btn-replace-all'); - CodeMirror.on(doReplaceAllButton, 'click', function (e) { - if (!searchField.value) { - searchField.focus(); - return; - } - var state = getSearchState(cm); - var query = parseQuery(searchField.value, state); - var withText = parseString(replaceField.value); - if (searchField.value.length > 1) { - state.replaceStarted = true; - } - if (state.replaceStarted) { - replaceAll(cm, query, withText); - state.replaceStarted = false; - } else { - startSearch(cm, state, searchField.value); - state.replaceStarted = true; - } - }); - } else { - searchField.value = deflt; - - searchField.focus(); - searchField.select(); - } -} - -function dialog(cm, text, shortText, deflt, f) { - if (cm.openDialog) - cm.openDialog(text, f, { value: deflt, selectValueOnOpen: true }); - else f(prompt(shortText, deflt)); -} - -function parseString(string) { - return string.replace(/\\(.)/g, function (_, ch) { - if (ch == 'n') return '\n'; - if (ch == 'r') return '\r'; - return ch; - }); -} - -function parseQuery(query, state) { - var emptyQuery = 'x^'; // matches nothing - if (query === '') { - query = emptyQuery; - } else { - if (state.regexp === false) { - query = parseString(query); - query = query.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, '\\$&'); - } - if (state.wholeWord) { - query = '\\b' + query + '\\b'; - } - } - - var regexp; - try { - regexp = new RegExp(query, state.caseInsensitive ? 'gi' : 'g'); - } catch (e) { - regexp = new RegExp(emptyQuery, 'g'); - } - // If the resulting regexp will match everything, do not use it - if (regexp.test('')) { - return new RegExp(emptyQuery, 'g'); - } - return regexp; -} - -function startSearch(cm, state, query) { - var searchDialog = document.querySelector('.CodeMirror-dialog'); - if (searchDialog) { - // check if the file has changed - let currentFileName = document.querySelector('.editor__file-name span') - ?.innerText; - - if (state.lastFileName !== currentFileName) { - state.lastFileName = currentFileName; - state.queryText = null; - state.lastQuery = null; - state.query = null; - cm.removeOverlay(state.overlay); - state.overlay = null; - - if (searchDialog) { - cm.display.wrapper.querySelector( - '.CodeMirror-search-results' - ).innerText = '0/0'; - } - } - - state.queryText = query; - state.lastQuery = query; - state.query = parseQuery(query, state); - cm.removeOverlay(state.overlay, state.caseInsensitive); - state.overlay = searchOverlay(state.query, state.caseInsensitive); - cm.addOverlay(state.overlay); - if (cm.showMatchesOnScrollbar) { - if (state.annotate) { - state.annotate.clear(); - state.annotate = null; - } - state.annotate = cm.showMatchesOnScrollbar( - state.query, - state.caseInsensitive - ); - } - - var cursor = getSearchCursor(cm, state.query); - cursor.findNext(); - var num_match = cm.state.search.annotate.matches.length; - if (num_match == 0) { - cm.display.wrapper.querySelector( - '.CodeMirror-search-results' - ).innerText = i18n.t('CodemirrorFindAndReplace.NoResults'); - cm.removeOverlay(state.overlay, state.caseInsensitive); - } else { - var next = - cm.state.search.annotate.matches.findIndex((s) => { - return ( - s.from.ch === cursor.from().ch && s.from.line === cursor.from().line - ); - }) + 1; - var text_match = next + '/' + num_match; - cm.display.wrapper.querySelector( - '.CodeMirror-search-results' - ).innerText = text_match; - } - } -} - -function doSearch(cm, rev, persistent, immediate, ignoreQuery) { - var state = getSearchState(cm); - if (!ignoreQuery && state.query) { - return findNext(cm, rev); - } - var q = cm.getSelection() || state.lastQuery; - var queryDialog = getQueryDialog(); - if (persistent && cm.openDialog) { - var hiding = null; - var searchNext = function (query, event) { - CodeMirror.e_stop(event); - if (!query) return; - if (query != state.queryText) { - startSearch(cm, state, query); - state.posFrom = state.posTo = cm.getCursor(); - } - if (hiding) hiding.style.opacity = 1; - findNext(cm, event.shiftKey, function (_, to) { - var dialog; - if ( - to.line < 3 && - document.querySelector && - (dialog = cm.display.wrapper.querySelector('.CodeMirror-dialog')) && - dialog.getBoundingClientRect().bottom - 4 > - cm.cursorCoords(to, 'window').top - ) - (hiding = dialog).style.opacity = 0.4; - }); - }; - persistentDialog( - cm, - queryDialog, - q, - searchNext, - false, - function (event, query) { - var keyName = CodeMirror.keyName(event); - var cmd = CodeMirror.keyMap[cm.getOption('keyMap')][keyName]; - if (!cmd) cmd = cm.getOption('extraKeys')[keyName]; - if ( - cmd == 'findNext' || - cmd == 'findPrev' || - cmd == 'findPersistentNext' || - cmd == 'findPersistentPrev' - ) { - CodeMirror.e_stop(event); - startSearch(cm, getSearchState(cm), query); - cm.execCommand(cmd); - } else if (cmd == 'find' || cmd == 'findPersistent') { - CodeMirror.e_stop(event); - searchNext(query, event); - } - } - ); - if (immediate && q) { - startSearch(cm, state, q); - findNext(cm, rev); - } - cm.on('change', function () { - var state = getSearchState(cm); - if (state.query) { - startSearch(cm, state, state.queryText); - } - }); - } else { - dialog(cm, queryDialog, 'Search for:', q, function (query) { - if (query && !state.query) - cm.operation(function () { - startSearch(cm, state, query); - state.posFrom = state.posTo = cm.getCursor(); - findNext(cm, rev); - }); - }); - } -} - -function doFindAndReplace( - cm, - rev, - persistent, - immediate, - ignoreQuery, - replaceOpened -) { - var state = getSearchState(cm); - if (!ignoreQuery && state.query) { - return findNext(cm, rev); - } - var q = cm.getSelection() || state.lastQuery; - var queryDialog = getQueryDialog(); - if (persistent && cm.openDialog) { - var hiding = null; - var searchNext = function (query, event) { - CodeMirror.e_stop(event); - if (!query) return; - if (query != state.queryText) { - startSearch(cm, state, query); - state.posFrom = state.posTo = cm.getCursor(); - } - if (hiding) hiding.style.opacity = 1; - findNext(cm, event.shiftKey, function (_, to) { - var dialog; - if ( - to.line < 3 && - document.querySelector && - (dialog = cm.display.wrapper.querySelector('.CodeMirror-dialog')) && - dialog.getBoundingClientRect().bottom - 4 > - cm.cursorCoords(to, 'window').top - ) - (hiding = dialog).style.opacity = 1; - }); - }; - persistentDialog( - cm, - queryDialog, - q, - searchNext, - replaceOpened, - function (event, query) { - var keyName = CodeMirror.keyName(event); - var cmd = CodeMirror.keyMap[cm.getOption('keyMap')][keyName]; - if (!cmd) cmd = cm.getOption('extraKeys')[keyName]; - if ( - cmd == 'findNext' || - cmd == 'findPrev' || - cmd == 'findPersistentNext' || - cmd == 'findPersistentPrev' - ) { - CodeMirror.e_stop(event); - startSearch(cm, getSearchState(cm), query); - cm.execCommand(cmd); - } else if (cmd == 'find' || cmd == 'findPersistent') { - CodeMirror.e_stop(event); - searchNext(query, event); - } - } - ); - if (immediate && q) { - startSearch(cm, state, q); - findNext(cm, rev); - } - } else { - dialog(cm, queryDialog, 'Search for:', q, function (query) { - if (query && !state.query) - cm.operation(function () { - startSearch(cm, state, query); - state.posFrom = state.posTo = cm.getCursor(); - findNext(cm, rev); - }); - }); - } -} - -function findNext(cm, rev, callback) { - cm.operation(function () { - var state = getSearchState(cm); - var cursor = getSearchCursor( - cm, - state.query, - rev ? state.posFrom : state.posTo - ); - if (!cursor.find(rev)) { - cursor = getSearchCursor( - cm, - state.query, - rev ? CodeMirror.Pos(cm.lastLine()) : CodeMirror.Pos(cm.firstLine(), 0) - ); - if (!cursor.find(rev)) { - cm.display.wrapper.querySelector( - '.CodeMirror-search-results' - ).innerText = i18n.t('CodemirrorFindAndReplace.NoResults'); - return; - } - } - cm.setSelection(cursor.from(), cursor.to()); - cm.scrollIntoView( - { from: cursor.from(), to: cursor.to() }, - cm.getScrollInfo().clientHeight / 2 - ); - state.posFrom = cursor.from(); - state.posTo = cursor.to(); - var num_match = cm.state.search.annotate.matches.length; - var next = - cm.state.search.annotate.matches.findIndex( - (s) => - s.from.ch === cursor.from().ch && s.from.line === cursor.from().line - ) + 1; - var text_match = next + '/' + num_match; - cm.display.wrapper.querySelector( - '.CodeMirror-search-results' - ).innerText = text_match; - if (callback) callback(cursor.from(), cursor.to()); - }); -} - -function clearSearch(cm) { - cm.operation(function () { - var state = getSearchState(cm); - state.replaceStarted = false; - if (!state.query) return; - state.query = state.queryText = null; - cm.removeOverlay(state.overlay); - if (state.annotate) { - state.annotate.clear(); - state.annotate = null; - } - }); -} - -function replaceAll(cm, query, text) { - cm.operation(function () { - for (var cursor = getSearchCursor(cm, query); cursor.findNext(); ) { - if (typeof query != 'string') { - var match = cm.getRange(cursor.from(), cursor.to()).match(query); - cursor.replace( - text.replace(/\$(\d)/g, function (_, i) { - return match[i]; - }) - ); - } else cursor.replace(text); - } - }); -} - -var getQueryDialog = function () { - return ` -
    -
    - -
    -
    -
    - -
    - -
    -
    - -
    -
    - - - -
    -
    -

    ${i18n.t( - 'CodemirrorFindAndReplace.NoResults' - )}

    - - -
    -
    - -
    -
    -
    -
    - `; -}; - -// CodeMirror.commands.findPersistent = function(cm) {doFindAndReplace(cm, false, true, false, true, false);}; -// CodeMirror.commands.findPersistentNext = function(cm) {doFindAndReplace(cm, false, true, false, true, false);}; -// CodeMirror.commands.findPersistentPrev = function(cm) {doFindAndReplace(cm, false, true, false, true, false);}; -// CodeMirror.commands.findNext = doFindAndReplace; -// CodeMirror.commands.findPrev = function(cm) {doFindAndReplace(cm, true);}; -// CodeMirror.commands.clearSearch = clearSearch; -// CodeMirror.commands.replace = function(cm) { doFindAndReplace(cm, false, true, false, true, true); }; - -CodeMirror.commands.find = function (cm) { - doSearch(cm); -}; -CodeMirror.commands.findPersistent = function (cm) { - doSearch(cm, false, true, false, true); -}; -CodeMirror.commands.findPersistentNext = function (cm) { - doSearch(cm, false, true, true); -}; -CodeMirror.commands.findPersistentPrev = function (cm) { - doSearch(cm, true, true, true); -}; -CodeMirror.commands.findNext = doSearch; -CodeMirror.commands.findPrev = function (cm) { - doSearch(cm, true); -}; -CodeMirror.commands.clearSearch = clearSearch; -CodeMirror.commands.replace = function (cm) { - doFindAndReplace(cm, false, true, false, true, true); -}; -CodeMirror.commands.replaceAll = function (cm) { - doFindAndReplace(cm, true); -}; diff --git a/client/utils/contextAwareHinter.js b/client/utils/contextAwareHinter.js index a90b88cd0e..f9fc11be39 100644 --- a/client/utils/contextAwareHinter.js +++ b/client/utils/contextAwareHinter.js @@ -4,17 +4,145 @@ import classMap from './p5-instance-methods-and-creators.json'; const scopeMap = require('./p5-scope-function-access-map.json'); -function getExpressionBeforeCursor(cm) { - const cursor = cm.getCursor(); - const line = cm.getLine(cursor.line); - const uptoCursor = line.slice(0, cursor.ch); +function getCurrentWordInfo(context) { + const word = context.matchBefore(/\w*/); + + if (!word) { + return { + text: '', + from: context.pos, + to: context.pos + }; + } + + return { + text: word.text || '', + from: word.from, + to: context.pos + }; +} + +function getExpressionBeforeCursor(state, pos) { + const line = state.doc.lineAt(pos); + const uptoCursor = line.text.slice(0, pos - line.from); const match = uptoCursor.match( /([a-zA-Z_$][\w$]*(?:\.[a-zA-Z_$][\w$]*)*)\.(?:[a-zA-Z_$][\w$]*)?$/ ); return match ? match[1] : null; } -export default function contextAwareHinter(cm, options = {}) { +function getTypedMemberInfo(state, pos) { + const line = state.doc.lineAt(pos); + const uptoCursor = line.text.slice(0, pos - line.from); + const dotMatch = uptoCursor.match(/\.([a-zA-Z_$][\w$]*)?$/); + + if (!dotMatch) { + return { + typed: '', + from: pos, + to: pos + }; + } + + const typed = dotMatch[1] || ''; + const methodStart = pos - dotMatch[0].length + 1; + + return { + typed, + from: methodStart, + to: pos + }; +} + +function formatPreview(label, params = []) { + if (!params.length) return `${label}()`; + + return `${label}(${params + .map((param) => (param.o ? `[${param.p}]` : param.p)) + .join(', ')})`; +} + +function makeBaseHintLookup(hints) { + const byLabel = new Map(); + + hints.forEach((hint) => { + if (hint?.label) { + byLabel.set(hint.label, hint); + } + }); + + return byLabel; +} + +function buildMethodOption(methodName, baseHint, range) { + const params = baseHint?.params || []; + + return { + label: methodName, + type: 'method', + kindLabel: baseHint?.kindLabel || 'fun', + params, + p5DocPath: baseHint?.p5DocPath, + preview: baseHint?.preview || formatPreview(methodName, params), + from: range.from, + to: range.to + }; +} + +function buildVarOrFunctionOption({ + name, + isFunc, + userDefinedFunctionMetadata, + blacklist, + range +}) { + const fnMeta = userDefinedFunctionMetadata[name]; + const params = fnMeta?.params || []; + + let preview; + + if (isFunc) { + if (fnMeta?.text) { + preview = formatPreview(fnMeta.text, params); + } else { + preview = formatPreview(name, params); + } + } else { + preview = undefined; + } + + const isBlacklisted = blacklist.includes(name); + + return { + label: fnMeta?.text || name, + type: isFunc ? 'method' : 'variable', + kindLabel: isFunc ? 'fun' : 'var', + params, + p5DocPath: undefined, + preview, + blacklisted: isBlacklisted, + warning: isBlacklisted ? '⚠️ use with caution in this context' : null, + from: range.from, + to: range.to + }; +} + +function buildGlobalHintOption(hint, blacklist, range) { + return { + ...hint, + blacklisted: blacklist.includes(hint.label), + warning: blacklist.includes(hint.label) + ? '⚠️ use with caution in this context' + : null, + from: range.from, + to: range.to + }; +} + +export default function contextAwareHinter(context, { hints = [] } = {}) { + const { state, pos } = context; + const cm = state.doc.toString(); + const { variableToP5ClassMap = {}, scopeToDeclaredVarsMap = {}, @@ -22,12 +150,9 @@ export default function contextAwareHinter(cm, options = {}) { userDefinedClassMetadata = {} } = p5CodeAstAnalyzer(cm) || {}; - const { hinter } = options; - if (!hinter || typeof hinter.search !== 'function') { - return []; - } + const baseHintLookup = makeBaseHintLookup(hints); - const baseExpression = getExpressionBeforeCursor(cm); + const baseExpression = getExpressionBeforeCursor(state, pos); if (baseExpression) { const className = variableToP5ClassMap[baseExpression]; @@ -38,61 +163,36 @@ export default function contextAwareHinter(cm, options = {}) { let methods = []; if (userClassEntry?.methods) { - const { methods: userMethods } = userClassEntry; - methods = userMethods; + methods = userClassEntry.methods; } else if (className && classMap[className]?.methods) { - const { methods: classMethods } = classMap[className]; - methods = classMethods; + methods = classMap[className].methods; } else { return []; } - const cursor = cm.getCursor(); - const lineText = cm.getLine(cursor.line); - const dotMatch = lineText - .slice(0, cursor.ch) - .match(/\.([a-zA-Z_$][\w$]*)?$/); - - let from = cursor; - if (dotMatch) { - const fullMatch = dotMatch[0]; - const methodStart = cursor.ch - fullMatch.length + 1; - from = { line: cursor.line, ch: methodStart }; - } else { - from = cursor; - } - - const to = { line: cursor.line, ch: cursor.ch }; - const typed = dotMatch?.[1]?.toLowerCase() || ''; - - const methodHints = methods - .filter((method) => method.toLowerCase().startsWith(typed)) - .map((method) => ({ - item: { - text: method, - type: 'fun', - isMethod: true - }, - displayText: method, - from, - to - })); - - return methodHints; + const memberInfo = getTypedMemberInfo(state, pos); + const typedLower = memberInfo.typed.toLowerCase(); + + const options = methods + .filter((method) => method.toLowerCase().startsWith(typedLower)) + .map((method) => + buildMethodOption(method, baseHintLookup.get(method), memberInfo) + ); + + return { + from: memberInfo.from, + to: memberInfo.to, + options, + filter: false + }; } - const { line, ch } = cm.getCursor(); - const { string } = cm.getTokenAt({ line, ch }); - const currentWord = string.trim(); - - const currentContext = getContext(cm); - const allHints = hinter.search(currentWord); + const wordInfo = getCurrentWordInfo(context); + const lowerCurrentWord = wordInfo.text.toLowerCase(); - // const whitelist = scopeMap[currentContext]?.whitelist || []; + const currentContext = getContext(cm, pos); const blacklist = scopeMap[currentContext]?.blacklist || []; - const lowerCurrentWord = currentWord.toLowerCase(); - function isInScope(varName) { return Object.entries(scopeToDeclaredVarsMap).some( ([scope, vars]) => @@ -103,13 +203,13 @@ export default function contextAwareHinter(cm, options = {}) { const allVarNames = Array.from( new Set( Object.values(scopeToDeclaredVarsMap) - .map((s) => Object.keys(s)) + .map((scopeVars) => Object.keys(scopeVars)) .flat() .filter((name) => typeof name === 'string') ) ); - const varHints = allVarNames + const localOptions = allVarNames .filter( (varName) => varName.toLowerCase().startsWith(lowerCurrentWord) && isInScope(varName) @@ -120,70 +220,58 @@ export default function contextAwareHinter(cm, options = {}) { (!scopeToDeclaredVarsMap[currentContext]?.[varName] && scopeToDeclaredVarsMap.global?.[varName] === 'fun'); - const baseItem = isFunc - ? { ...userDefinedFunctionMetadata[varName] } - : { - text: varName, - type: 'var', - params: [], - p5: false - }; - - return { - item: baseItem, - isBlacklisted: blacklist.includes(varName) - }; + return buildVarOrFunctionOption({ + name: varName, + isFunc, + userDefinedFunctionMetadata, + blacklist, + range: wordInfo + }); }); - const filteredHints = allHints + const globalOptions = hints .filter( - (h) => - h && - h.item && - typeof h.item.text === 'string' && - h.item.text.toLowerCase().startsWith(lowerCurrentWord) + (hint) => + hint && + typeof hint.label === 'string' && + hint.label.toLowerCase().startsWith(lowerCurrentWord) ) - .map((hint) => { - const name = hint.item?.text || ''; - const isBlacklisted = blacklist.includes(name); - - return { - ...hint, - isBlacklisted - }; - }); + .map((hint) => buildGlobalHintOption(hint, blacklist, wordInfo)); - const combinedHints = [...varHints, ...filteredHints]; + const combinedOptions = [...localOptions, ...globalOptions]; const typePriority = { - fun: 0, - var: 1, + method: 0, + variable: 1, keyword: 2, - other: 3 + constant: 3, + boolean: 4, + obj: 5, + other: 6 }; - const sorted = combinedHints.sort((a, b) => { - const nameA = a.item?.text || ''; - const nameB = b.item?.text || ''; - const typeA = a.item?.type || 'other'; - const typeB = b.item?.type || 'other'; - - const isBlacklistedA = a.isBlacklisted ? 1 : 0; - const isBlacklistedB = b.isBlacklisted ? 1 : 0; - - const typeScoreA = typePriority[typeA] ?? typePriority.other; - const typeScoreB = typePriority[typeB] ?? typePriority.other; + combinedOptions.sort((a, b) => { + const isBlacklistedA = a.blacklisted ? 1 : 0; + const isBlacklistedB = b.blacklisted ? 1 : 0; if (isBlacklistedA !== isBlacklistedB) { return isBlacklistedA - isBlacklistedB; } - if (typeScoreA !== typeScoreB) { - return typeScoreA - typeScoreB; + const typeA = typePriority[a.type] ?? typePriority.other; + const typeB = typePriority[b.type] ?? typePriority.other; + + if (typeA !== typeB) { + return typeA - typeB; } - return nameA.localeCompare(nameB); + return a.label.localeCompare(b.label); }); - return sorted; + return { + from: wordInfo.from, + to: wordInfo.to, + options: combinedOptions, + filter: false + }; } diff --git a/client/utils/getContext.js b/client/utils/getContext.js index beddef71f7..2e9a11ebdc 100644 --- a/client/utils/getContext.js +++ b/client/utils/getContext.js @@ -1,11 +1,7 @@ const parser = require('@babel/parser'); const traverse = require('@babel/traverse').default; -export default function getContext(_cm) { - const code = _cm.getValue(); - const cursor = _cm.getCursor(); - const offset = _cm.indexFromPos(cursor); - +export default function getContext(code, pos) { let ast; try { ast = parser.parse(code, { @@ -21,7 +17,7 @@ export default function getContext(_cm) { traverse(ast, { Function(path) { const { node } = path; - if (offset >= node.start && offset <= node.end) { + if (pos >= node.start && pos <= node.end) { if (node.id && node.id.name) { context = node.id.name; } else { diff --git a/client/utils/htmlmixed.js b/client/utils/htmlmixed.js deleted file mode 100644 index 23a0eacf05..0000000000 --- a/client/utils/htmlmixed.js +++ /dev/null @@ -1,153 +0,0 @@ -// CodeMirror, copyright (c) by Marijn Haverbeke and others -// Distributed under an MIT license: http://codemirror.net/LICENSE -/* eslint-disable */ - -(function(mod) { - if (typeof exports == "object" && typeof module == "object") // CommonJS - mod(require("codemirror"), require("codemirror/mode/xml/xml"), require("./p5-javascript"), require("codemirror/mode/css/css")); - else if (typeof define == "function" && define.amd) // AMD - define(["codemirror", "codemirror/mode/xml/xml", "./p5-javascript", "codemirror/mode/css/css"], mod); - else // Plain browser env - mod(CodeMirror); -})(function(CodeMirror) { - "use strict"; - - var defaultTags = { - script: [ - ["lang", /(javascript|babel)/i, "javascript"], - ["type", /^(?:text|application)\/(?:x-)?(?:java|ecma)script$|^$/i, "javascript"], - ["type", /./, "text/plain"], - [null, null, "javascript"] - ], - style: [ - ["lang", /^css$/i, "css"], - ["type", /^(text\/)?(x-)?(stylesheet|css)$/i, "css"], - ["type", /./, "text/plain"], - [null, null, "css"] - ] - }; - - function maybeBackup(stream, pat, style) { - var cur = stream.current(), close = cur.search(pat); - if (close > -1) { - stream.backUp(cur.length - close); - } else if (cur.match(/<\/?$/)) { - stream.backUp(cur.length); - if (!stream.match(pat, false)) stream.match(cur); - } - return style; - } - - var attrRegexpCache = {}; - function getAttrRegexp(attr) { - var regexp = attrRegexpCache[attr]; - if (regexp) return regexp; - return attrRegexpCache[attr] = new RegExp("\\s+" + attr + "\\s*=\\s*('|\")?([^'\"]+)('|\")?\\s*"); - } - - function getAttrValue(text, attr) { - var match = text.match(getAttrRegexp(attr)) - return match ? /^\s*(.*?)\s*$/.exec(match[2])[1] : "" - } - - function getTagRegexp(tagName, anchored) { - return new RegExp((anchored ? "^" : "") + "<\/\s*" + tagName + "\s*>", "i"); - } - - function addTags(from, to) { - for (var tag in from) { - var dest = to[tag] || (to[tag] = []); - var source = from[tag]; - for (var i = source.length - 1; i >= 0; i--) - dest.unshift(source[i]) - } - } - - function findMatchingMode(tagInfo, tagText) { - for (var i = 0; i < tagInfo.length; i++) { - var spec = tagInfo[i]; - if (!spec[0] || spec[1].test(getAttrValue(tagText, spec[0]))) return spec[2]; - } - } - - CodeMirror.defineMode("htmlmixed", function (config, parserConfig) { - var htmlMode = CodeMirror.getMode(config, { - name: "xml", - htmlMode: true, - multilineTagIndentFactor: parserConfig.multilineTagIndentFactor, - multilineTagIndentPastTag: parserConfig.multilineTagIndentPastTag - }); - - var tags = {}; - var configTags = parserConfig && parserConfig.tags, configScript = parserConfig && parserConfig.scriptTypes; - addTags(defaultTags, tags); - if (configTags) addTags(configTags, tags); - if (configScript) for (var i = configScript.length - 1; i >= 0; i--) - tags.script.unshift(["type", configScript[i].matches, configScript[i].mode]) - - function html(stream, state) { - var style = htmlMode.token(stream, state.htmlState), tag = /\btag\b/.test(style), tagName - if (tag && !/[<>\s\/]/.test(stream.current()) && - (tagName = state.htmlState.tagName && state.htmlState.tagName.toLowerCase()) && - tags.hasOwnProperty(tagName)) { - state.inTag = tagName + " " - } else if (state.inTag && tag && />$/.test(stream.current())) { - var inTag = /^([\S]+) (.*)/.exec(state.inTag) - state.inTag = null - var modeSpec = stream.current() == ">" && findMatchingMode(tags[inTag[1]], inTag[2]) - var mode = CodeMirror.getMode(config, modeSpec) - var endTagA = getTagRegexp(inTag[1], true), endTag = getTagRegexp(inTag[1], false); - state.token = function (stream, state) { - if (stream.match(endTagA, false)) { - state.token = html; - state.localState = state.localMode = null; - return null; - } - return maybeBackup(stream, endTag, state.localMode.token(stream, state.localState)); - }; - state.localMode = mode; - state.localState = CodeMirror.startState(mode, htmlMode.indent(state.htmlState, "")); - } else if (state.inTag) { - state.inTag += stream.current() - if (stream.eol()) state.inTag += " " - } - return style; - }; - - return { - startState: function () { - var state = CodeMirror.startState(htmlMode); - return {token: html, inTag: null, localMode: null, localState: null, htmlState: state}; - }, - - copyState: function (state) { - var local; - if (state.localState) { - local = CodeMirror.copyState(state.localMode, state.localState); - } - return {token: state.token, inTag: state.inTag, - localMode: state.localMode, localState: local, - htmlState: CodeMirror.copyState(htmlMode, state.htmlState)}; - }, - - token: function (stream, state) { - return state.token(stream, state); - }, - - indent: function (state, textAfter) { - if (!state.localMode || /^\s*<\//.test(textAfter)) - return htmlMode.indent(state.htmlState, textAfter); - else if (state.localMode.indent) - return state.localMode.indent(state.localState, textAfter); - else - return CodeMirror.Pass; - }, - - innerMode: function (state) { - return {state: state.localState || state.htmlState, mode: state.localMode || htmlMode}; - } - }; - }, "xml", "javascript", "css"); - - CodeMirror.defineMIME("text/html", "htmlmixed"); -}); diff --git a/client/utils/p5-hinter.js b/client/utils/p5-hinter.js index 25d68c59f2..dee7c9870b 100644 --- a/client/utils/p5-hinter.js +++ b/client/utils/p5-hinter.js @@ -1,1734 +1,3 @@ /* eslint-disable */ /* generated: do not edit! helper file for hinter. generated by update-p5-hinter script */ -exports.p5Hinter = [ - { - text: 'describe', - type: 'fun', - params: [ - { p: 'text', o: false }, - { p: 'display', o: true } - ], - p5: true - }, - { - text: 'describeElement', - type: 'fun', - params: [ - { p: 'name', o: false }, - { p: 'text', o: false }, - { p: 'display', o: true } - ], - p5: true - }, - { - text: 'textOutput', - type: 'fun', - params: [{ p: 'display', o: true }], - p5: true - }, - { - text: 'gridOutput', - type: 'fun', - params: [{ p: 'display', o: true }], - p5: true - }, - { text: 'alpha', type: 'fun', params: [{ p: 'color', o: false }], p5: true }, - { text: 'blue', type: 'fun', params: [{ p: 'color', o: false }], p5: true }, - { - text: 'brightness', - type: 'fun', - params: [{ p: 'color', o: false }], - p5: true - }, - { text: 'color', type: 'fun', p5: true }, - { text: 'green', type: 'fun', params: [{ p: 'color', o: false }], p5: true }, - { text: 'hue', type: 'fun', params: [{ p: 'color', o: false }], p5: true }, - { - text: 'lerpColor', - type: 'fun', - params: [ - { p: 'c1', o: false }, - { p: 'c2', o: false }, - { p: 'amt', o: false } - ], - p5: true - }, - { - text: 'lightness', - type: 'fun', - params: [{ p: 'color', o: false }], - p5: true - }, - { text: 'red', type: 'fun', params: [{ p: 'color', o: false }], p5: true }, - { - text: 'saturation', - type: 'fun', - params: [{ p: 'color', o: false }], - p5: true - }, - { - text: 'beginClip', - type: 'fun', - params: [{ p: 'options', o: true }], - p5: true - }, - { text: 'endClip', type: 'fun', p5: true }, - { - text: 'clip', - type: 'fun', - params: [ - { p: 'callback', o: false }, - { p: 'options', o: true } - ], - p5: true - }, - { text: 'background', type: 'fun', p5: true }, - { - text: 'clear', - type: 'fun', - params: [ - { p: 'r', o: true }, - { p: 'g', o: true }, - { p: 'b', o: true }, - { p: 'a', o: true } - ], - p5: true - }, - { text: 'colorMode', type: 'fun', p5: true }, - { text: 'fill', type: 'fun', p5: true }, - { text: 'noFill', type: 'fun', p5: true }, - { text: 'noStroke', type: 'fun', p5: true }, - { text: 'stroke', type: 'fun', p5: true }, - { - text: 'erase', - type: 'fun', - params: [ - { p: 'strengthFill', o: true }, - { p: 'strengthStroke', o: true } - ], - p5: true - }, - { text: 'noErase', type: 'fun', p5: true }, - { - text: 'arc', - type: 'fun', - params: [ - { p: 'x', o: false }, - { p: 'y', o: false }, - { p: 'w', o: false }, - { p: 'h', o: false }, - { p: 'start', o: false }, - { p: 'stop', o: false }, - { p: 'mode', o: true }, - { p: 'detail', o: true } - ], - p5: true - }, - { text: 'ellipse', type: 'fun', p5: true }, - { - text: 'circle', - type: 'fun', - params: [ - { p: 'x', o: false }, - { p: 'y', o: false }, - { p: 'd', o: false } - ], - p5: true - }, - { text: 'line', type: 'fun', p5: true }, - { text: 'point', type: 'fun', p5: true }, - { text: 'quad', type: 'fun', p5: true }, - { text: 'rect', type: 'fun', p5: true }, - { - text: 'square', - type: 'fun', - params: [ - { p: 'x', o: false }, - { p: 'y', o: false }, - { p: 's', o: false }, - { p: 'tl', o: true }, - { p: 'tr', o: true }, - { p: 'br', o: true }, - { p: 'bl', o: true } - ], - p5: true - }, - { - text: 'triangle', - type: 'fun', - params: [ - { p: 'x1', o: false }, - { p: 'y1', o: false }, - { p: 'x2', o: false }, - { p: 'y2', o: false }, - { p: 'x3', o: false }, - { p: 'y3', o: false } - ], - p5: true - }, - { - text: 'ellipseMode', - type: 'fun', - params: [{ p: 'mode', o: false }], - p5: true - }, - { text: 'noSmooth', type: 'fun', p5: true }, - { - text: 'rectMode', - type: 'fun', - params: [{ p: 'mode', o: false }], - p5: true - }, - { text: 'smooth', type: 'fun', p5: true }, - { - text: 'strokeCap', - type: 'fun', - params: [{ p: 'cap', o: false }], - p5: true - }, - { - text: 'strokeJoin', - type: 'fun', - params: [{ p: 'join', o: false }], - p5: true - }, - { - text: 'strokeWeight', - type: 'fun', - params: [{ p: 'weight', o: false }], - p5: true - }, - { text: 'bezier', type: 'fun', p5: true }, - { - text: 'bezierDetail', - type: 'fun', - params: [{ p: 'detail', o: false }], - p5: true - }, - { - text: 'bezierPoint', - type: 'fun', - params: [ - { p: 'a', o: false }, - { p: 'b', o: false }, - { p: 'c', o: false }, - { p: 'd', o: false }, - { p: 't', o: false } - ], - p5: true - }, - { - text: 'bezierTangent', - type: 'fun', - params: [ - { p: 'a', o: false }, - { p: 'b', o: false }, - { p: 'c', o: false }, - { p: 'd', o: false }, - { p: 't', o: false } - ], - p5: true - }, - { text: 'curve', type: 'fun', p5: true }, - { - text: 'curveDetail', - type: 'fun', - params: [{ p: 'resolution', o: false }], - p5: true - }, - { - text: 'curveTightness', - type: 'fun', - params: [{ p: 'amount', o: false }], - p5: true - }, - { - text: 'curvePoint', - type: 'fun', - params: [ - { p: 'a', o: false }, - { p: 'b', o: false }, - { p: 'c', o: false }, - { p: 'd', o: false }, - { p: 't', o: false } - ], - p5: true - }, - { - text: 'curveTangent', - type: 'fun', - params: [ - { p: 'a', o: false }, - { p: 'b', o: false }, - { p: 'c', o: false }, - { p: 'd', o: false }, - { p: 't', o: false } - ], - p5: true - }, - { text: 'beginContour', type: 'fun', p5: true }, - { - text: 'beginShape', - type: 'fun', - params: [{ p: 'kind', o: true }], - p5: true - }, - { text: 'bezierVertex', type: 'fun', p5: true }, - { text: 'curveVertex', type: 'fun', p5: true }, - { text: 'endContour', type: 'fun', p5: true }, - { - text: 'endShape', - type: 'fun', - params: [ - { p: 'mode', o: true }, - { p: 'count', o: true } - ], - p5: true - }, - { text: 'quadraticVertex', type: 'fun', p5: true }, - { text: 'vertex', type: 'fun', p5: true }, - { text: 'normal', type: 'fun', p5: true }, - { text: 'VERSION', type: 'var', params: [], p5: true }, - { text: 'P2D', type: 'var', params: [], p5: true }, - { text: 'WEBGL', type: 'var', params: [], p5: true }, - { text: 'WEBGL2', type: 'var', params: [], p5: true }, - { text: 'ARROW', type: 'var', params: [], p5: true }, - { text: 'CROSS', type: 'var', params: [], p5: true }, - { text: 'HAND', type: 'var', params: [], p5: true }, - { text: 'MOVE', type: 'var', params: [], p5: true }, - { text: 'TEXT', type: 'var', params: [], p5: true }, - { text: 'WAIT', type: 'var', params: [], p5: true }, - { text: 'HALF_PI', type: 'var', params: [], p5: true }, - { text: 'PI', type: 'var', params: [], p5: true }, - { text: 'QUARTER_PI', type: 'var', params: [], p5: true }, - { text: 'TAU', type: 'var', params: [], p5: true }, - { text: 'TWO_PI', type: 'var', params: [], p5: true }, - { text: 'DEGREES', type: 'var', params: [], p5: true }, - { text: 'RADIANS', type: 'var', params: [], p5: true }, - { text: 'CORNER', type: 'var', params: [], p5: true }, - { text: 'CORNERS', type: 'var', params: [], p5: true }, - { text: 'RADIUS', type: 'var', params: [], p5: true }, - { text: 'RIGHT', type: 'var', params: [], p5: true }, - { text: 'LEFT', type: 'var', params: [], p5: true }, - { text: 'CENTER', type: 'var', params: [], p5: true }, - { text: 'TOP', type: 'var', params: [], p5: true }, - { text: 'BOTTOM', type: 'var', params: [], p5: true }, - { text: 'BASELINE', type: 'var', params: [], p5: true }, - { text: 'POINTS', type: 'var', params: [], p5: true }, - { text: 'LINES', type: 'var', params: [], p5: true }, - { text: 'LINE_STRIP', type: 'var', params: [], p5: true }, - { text: 'LINE_LOOP', type: 'var', params: [], p5: true }, - { text: 'TRIANGLES', type: 'var', params: [], p5: true }, - { text: 'TRIANGLE_FAN', type: 'var', params: [], p5: true }, - { text: 'TRIANGLE_STRIP', type: 'var', params: [], p5: true }, - { text: 'QUADS', type: 'var', params: [], p5: true }, - { text: 'QUAD_STRIP', type: 'var', params: [], p5: true }, - { text: 'TESS', type: 'var', params: [], p5: true }, - { text: 'CLOSE', type: 'var', params: [], p5: true }, - { text: 'OPEN', type: 'var', params: [], p5: true }, - { text: 'CHORD', type: 'var', params: [], p5: true }, - { text: 'PIE', type: 'var', params: [], p5: true }, - { text: 'PROJECT', type: 'var', params: [], p5: true }, - { text: 'SQUARE', type: 'var', params: [], p5: true }, - { text: 'ROUND', type: 'var', params: [], p5: true }, - { text: 'BEVEL', type: 'var', params: [], p5: true }, - { text: 'MITER', type: 'var', params: [], p5: true }, - { text: 'RGB', type: 'var', params: [], p5: true }, - { text: 'HSB', type: 'var', params: [], p5: true }, - { text: 'HSL', type: 'var', params: [], p5: true }, - { text: 'AUTO', type: 'var', params: [], p5: true }, - { text: 'ALT', type: 'var', params: [], p5: true }, - { text: 'BACKSPACE', type: 'var', params: [], p5: true }, - { text: 'CONTROL', type: 'var', params: [], p5: true }, - { text: 'DELETE', type: 'var', params: [], p5: true }, - { text: 'DOWN_ARROW', type: 'var', params: [], p5: true }, - { text: 'ENTER', type: 'var', params: [], p5: true }, - { text: 'ESCAPE', type: 'var', params: [], p5: true }, - { text: 'LEFT_ARROW', type: 'var', params: [], p5: true }, - { text: 'OPTION', type: 'var', params: [], p5: true }, - { text: 'RETURN', type: 'var', params: [], p5: true }, - { text: 'RIGHT_ARROW', type: 'var', params: [], p5: true }, - { text: 'SHIFT', type: 'var', params: [], p5: true }, - { text: 'TAB', type: 'var', params: [], p5: true }, - { text: 'UP_ARROW', type: 'var', params: [], p5: true }, - { text: 'BLEND', type: 'var', params: [], p5: true }, - { text: 'REMOVE', type: 'var', params: [], p5: true }, - { text: 'ADD', type: 'var', params: [], p5: true }, - { text: 'DARKEST', type: 'var', params: [], p5: true }, - { text: 'LIGHTEST', type: 'var', params: [], p5: true }, - { text: 'DIFFERENCE', type: 'var', params: [], p5: true }, - { text: 'SUBTRACT', type: 'var', params: [], p5: true }, - { text: 'EXCLUSION', type: 'var', params: [], p5: true }, - { text: 'MULTIPLY', type: 'var', params: [], p5: true }, - { text: 'SCREEN', type: 'var', params: [], p5: true }, - { text: 'REPLACE', type: 'var', params: [], p5: true }, - { text: 'OVERLAY', type: 'var', params: [], p5: true }, - { text: 'HARD_LIGHT', type: 'var', params: [], p5: true }, - { text: 'SOFT_LIGHT', type: 'var', params: [], p5: true }, - { text: 'DODGE', type: 'var', params: [], p5: true }, - { text: 'BURN', type: 'var', params: [], p5: true }, - { text: 'THRESHOLD', type: 'var', params: [], p5: true }, - { text: 'GRAY', type: 'var', params: [], p5: true }, - { text: 'OPAQUE', type: 'var', params: [], p5: true }, - { text: 'INVERT', type: 'var', params: [], p5: true }, - { text: 'POSTERIZE', type: 'var', params: [], p5: true }, - { text: 'DILATE', type: 'var', params: [], p5: true }, - { text: 'ERODE', type: 'var', params: [], p5: true }, - { text: 'BLUR', type: 'var', params: [], p5: true }, - { text: 'NORMAL', type: 'var', params: [], p5: true }, - { text: 'ITALIC', type: 'var', params: [], p5: true }, - { text: 'BOLD', type: 'var', params: [], p5: true }, - { text: 'BOLDITALIC', type: 'var', params: [], p5: true }, - { text: 'CHAR', type: 'var', params: [], p5: true }, - { text: 'WORD', type: 'var', params: [], p5: true }, - { text: 'LINEAR', type: 'var', params: [], p5: true }, - { text: 'QUADRATIC', type: 'var', params: [], p5: true }, - { text: 'BEZIER', type: 'var', params: [], p5: true }, - { text: 'CURVE', type: 'var', params: [], p5: true }, - { text: 'STROKE', type: 'var', params: [], p5: true }, - { text: 'FILL', type: 'var', params: [], p5: true }, - { text: 'TEXTURE', type: 'var', params: [], p5: true }, - { text: 'IMMEDIATE', type: 'var', params: [], p5: true }, - { text: 'IMAGE', type: 'var', params: [], p5: true }, - { text: 'NEAREST', type: 'var', params: [], p5: true }, - { text: 'REPEAT', type: 'var', params: [], p5: true }, - { text: 'CLAMP', type: 'var', params: [], p5: true }, - { text: 'MIRROR', type: 'var', params: [], p5: true }, - { text: 'FLAT', type: 'var', params: [], p5: true }, - { text: 'SMOOTH', type: 'var', params: [], p5: true }, - { text: 'LANDSCAPE', type: 'var', params: [], p5: true }, - { text: 'PORTRAIT', type: 'var', params: [], p5: true }, - { text: 'GRID', type: 'var', params: [], p5: true }, - { text: 'AXES', type: 'var', params: [], p5: true }, - { text: 'LABEL', type: 'var', params: [], p5: true }, - { text: 'FALLBACK', type: 'var', params: [], p5: true }, - { text: 'CONTAIN', type: 'var', params: [], p5: true }, - { text: 'COVER', type: 'var', params: [], p5: true }, - { text: 'UNSIGNED_BYTE', type: 'var', params: [], p5: true }, - { text: 'UNSIGNED_INT', type: 'var', params: [], p5: true }, - { text: 'FLOAT', type: 'var', params: [], p5: true }, - { text: 'HALF_FLOAT', type: 'var', params: [], p5: true }, - { text: 'RGBA', type: 'var', params: [], p5: true }, - { - text: 'print', - type: 'fun', - params: [{ p: 'contents', o: false }], - p5: true - }, - { text: 'frameCount', type: 'var', params: [], p5: true }, - { text: 'deltaTime', type: 'var', params: [], p5: true }, - { text: 'focused', type: 'var', params: [], p5: true }, - { - text: 'cursor', - type: 'fun', - params: [ - { p: 'type', o: false }, - { p: 'x', o: true }, - { p: 'y', o: true } - ], - p5: true - }, - { text: 'frameRate', type: 'fun', p5: true }, - { text: 'getTargetFrameRate', type: 'fun', p5: true }, - { text: 'noCursor', type: 'fun', p5: true }, - { text: 'webglVersion', type: 'var', params: [], p5: true }, - { text: 'displayWidth', type: 'var', params: [], p5: true }, - { text: 'displayHeight', type: 'var', params: [], p5: true }, - { text: 'windowWidth', type: 'var', params: [], p5: true }, - { text: 'windowHeight', type: 'var', params: [], p5: true }, - { - text: 'windowResized', - type: 'fun', - params: [{ p: 'event', o: true }], - p5: true - }, - { text: 'width', type: 'var', params: [], p5: true }, - { text: 'height', type: 'var', params: [], p5: true }, - { - text: 'fullscreen', - type: 'fun', - params: [{ p: 'val', o: true }], - p5: true - }, - { text: 'pixelDensity', type: 'fun', p5: true }, - { text: 'displayDensity', type: 'fun', p5: true }, - { text: 'getURL', type: 'fun', p5: true }, - { text: 'getURLPath', type: 'fun', p5: true }, - { text: 'getURLParams', type: 'fun', p5: true }, - { text: 'preload', type: 'fun', p5: true }, - { text: 'setup', type: 'fun', p5: true }, - { text: 'draw', type: 'fun', p5: true }, - { text: 'remove', type: 'fun', p5: true }, - { text: 'disableFriendlyErrors', type: 'var', params: [], p5: true }, - { text: 'createCanvas', type: 'fun', p5: true }, - { - text: 'resizeCanvas', - type: 'fun', - params: [ - { p: 'width', o: false }, - { p: 'height', o: false }, - { p: 'noRedraw', o: true } - ], - p5: true - }, - { text: 'noCanvas', type: 'fun', p5: true }, - { text: 'createGraphics', type: 'fun', p5: true }, - { - text: 'createFramebuffer', - type: 'fun', - params: [{ p: 'options', o: true }], - p5: true - }, - { - text: 'clearDepth', - type: 'fun', - params: [{ p: 'depth', o: true }], - p5: true - }, - { - text: 'blendMode', - type: 'fun', - params: [{ p: 'mode', o: false }], - p5: true - }, - { text: 'drawingContext', type: 'var', params: [], p5: true }, - { text: 'noLoop', type: 'fun', p5: true }, - { text: 'loop', type: 'fun', p5: true }, - { text: 'isLooping', type: 'fun', p5: true }, - { text: 'push', type: 'fun', p5: true }, - { text: 'pop', type: 'fun', p5: true }, - { text: 'redraw', type: 'fun', params: [{ p: 'n', o: true }], p5: true }, - { - text: 'p5', - type: 'fun', - params: [ - { p: 'sketch', o: false }, - { p: 'node', o: false } - ], - p5: true - }, - { text: 'applyMatrix', type: 'fun', p5: true }, - { text: 'resetMatrix', type: 'fun', p5: true }, - { - text: 'rotate', - type: 'fun', - params: [ - { p: 'angle', o: false }, - { p: 'axis', o: true } - ], - p5: true - }, - { - text: 'rotateX', - type: 'fun', - params: [{ p: 'angle', o: false }], - p5: true - }, - { - text: 'rotateY', - type: 'fun', - params: [{ p: 'angle', o: false }], - p5: true - }, - { - text: 'rotateZ', - type: 'fun', - params: [{ p: 'angle', o: false }], - p5: true - }, - { text: 'scale', type: 'fun', p5: true }, - { text: 'shearX', type: 'fun', params: [{ p: 'angle', o: false }], p5: true }, - { text: 'shearY', type: 'fun', params: [{ p: 'angle', o: false }], p5: true }, - { text: 'translate', type: 'fun', p5: true }, - { - text: 'storeItem', - type: 'fun', - params: [ - { p: 'key', o: false }, - { p: 'value', o: false } - ], - p5: true - }, - { text: 'getItem', type: 'fun', params: [{ p: 'key', o: false }], p5: true }, - { text: 'clearStorage', type: 'fun', p5: true }, - { - text: 'removeItem', - type: 'fun', - params: [{ p: 'key', o: false }], - p5: true - }, - { text: 'createStringDict', type: 'fun', p5: true }, - { text: 'createNumberDict', type: 'fun', p5: true }, - { - text: 'select', - type: 'fun', - params: [ - { p: 'selectors', o: false }, - { p: 'container', o: true } - ], - p5: true - }, - { - text: 'selectAll', - type: 'fun', - params: [ - { p: 'selectors', o: false }, - { p: 'container', o: true } - ], - p5: true - }, - { text: 'removeElements', type: 'fun', p5: true }, - { text: 'changed', type: 'fun', params: [{ p: 'fxn', o: false }], p5: true }, - { text: 'input', type: 'fun', params: [{ p: 'fxn', o: false }], p5: true }, - { - text: 'createDiv', - type: 'fun', - params: [{ p: 'html', o: true }], - p5: true - }, - { text: 'createP', type: 'fun', params: [{ p: 'html', o: true }], p5: true }, - { - text: 'createSpan', - type: 'fun', - params: [{ p: 'html', o: true }], - p5: true - }, - { text: 'createImg', type: 'fun', p5: true }, - { - text: 'createA', - type: 'fun', - params: [ - { p: 'href', o: false }, - { p: 'html', o: false }, - { p: 'target', o: true } - ], - p5: true - }, - { - text: 'createSlider', - type: 'fun', - params: [ - { p: 'min', o: false }, - { p: 'max', o: false }, - { p: 'value', o: true }, - { p: 'step', o: true } - ], - p5: true - }, - { - text: 'createButton', - type: 'fun', - params: [ - { p: 'label', o: false }, - { p: 'value', o: true } - ], - p5: true - }, - { - text: 'createCheckbox', - type: 'fun', - params: [ - { p: 'label', o: true }, - { p: 'value', o: true } - ], - p5: true - }, - { text: 'createSelect', type: 'fun', p5: true }, - { text: 'createRadio', type: 'fun', p5: true }, - { - text: 'createColorPicker', - type: 'fun', - params: [{ p: 'value', o: true }], - p5: true - }, - { text: 'createInput', type: 'fun', p5: true }, - { - text: 'createFileInput', - type: 'fun', - params: [ - { p: 'callback', o: false }, - { p: 'multiple', o: true } - ], - p5: true - }, - { - text: 'createVideo', - type: 'fun', - params: [ - { p: 'src', o: false }, - { p: 'callback', o: true } - ], - p5: true - }, - { - text: 'createAudio', - type: 'fun', - params: [ - { p: 'src', o: true }, - { p: 'callback', o: true } - ], - p5: true - }, - { - text: 'createCapture', - type: 'fun', - params: [ - { p: 'type', o: true }, - { p: 'flipped', o: true }, - { p: 'callback', o: true } - ], - p5: true - }, - { - text: 'createElement', - type: 'fun', - params: [ - { p: 'tag', o: false }, - { p: 'content', o: true } - ], - p5: true - }, - { text: 'deviceOrientation', type: 'var', params: [], p5: true }, - { text: 'accelerationX', type: 'var', params: [], p5: true }, - { text: 'accelerationY', type: 'var', params: [], p5: true }, - { text: 'accelerationZ', type: 'var', params: [], p5: true }, - { text: 'pAccelerationX', type: 'var', params: [], p5: true }, - { text: 'pAccelerationY', type: 'var', params: [], p5: true }, - { text: 'pAccelerationZ', type: 'var', params: [], p5: true }, - { text: 'rotationX', type: 'var', params: [], p5: true }, - { text: 'rotationY', type: 'var', params: [], p5: true }, - { text: 'rotationZ', type: 'var', params: [], p5: true }, - { text: 'pRotationX', type: 'var', params: [], p5: true }, - { text: 'pRotationY', type: 'var', params: [], p5: true }, - { text: 'pRotationZ', type: 'var', params: [], p5: true }, - { text: 'turnAxis', type: 'var', params: [], p5: true }, - { - text: 'setMoveThreshold', - type: 'fun', - params: [{ p: 'value', o: false }], - p5: true - }, - { - text: 'setShakeThreshold', - type: 'fun', - params: [{ p: 'value', o: false }], - p5: true - }, - { text: 'deviceMoved', type: 'fun', p5: true }, - { text: 'deviceTurned', type: 'fun', p5: true }, - { text: 'deviceShaken', type: 'fun', p5: true }, - { text: 'keyIsPressed', type: 'var', params: [], p5: true }, - { text: 'key', type: 'var', params: [], p5: true }, - { text: 'keyCode', type: 'var', params: [], p5: true }, - { - text: 'keyPressed', - type: 'fun', - params: [{ p: 'event', o: true }], - p5: true - }, - { - text: 'keyReleased', - type: 'fun', - params: [{ p: 'event', o: true }], - p5: true - }, - { - text: 'keyTyped', - type: 'fun', - params: [{ p: 'event', o: true }], - p5: true - }, - { - text: 'keyIsDown', - type: 'fun', - params: [{ p: 'code', o: false }], - p5: true - }, - { text: 'movedX', type: 'var', params: [], p5: true }, - { text: 'movedY', type: 'var', params: [], p5: true }, - { text: 'mouseX', type: 'var', params: [], p5: true }, - { text: 'mouseY', type: 'var', params: [], p5: true }, - { text: 'pmouseX', type: 'var', params: [], p5: true }, - { text: 'pmouseY', type: 'var', params: [], p5: true }, - { text: 'winMouseX', type: 'var', params: [], p5: true }, - { text: 'winMouseY', type: 'var', params: [], p5: true }, - { text: 'pwinMouseX', type: 'var', params: [], p5: true }, - { text: 'pwinMouseY', type: 'var', params: [], p5: true }, - { text: 'mouseButton', type: 'var', params: [], p5: true }, - { text: 'mouseIsPressed', type: 'var', params: [], p5: true }, - { - text: 'mouseMoved', - type: 'fun', - params: [{ p: 'event', o: true }], - p5: true - }, - { - text: 'mouseDragged', - type: 'fun', - params: [{ p: 'event', o: true }], - p5: true - }, - { - text: 'mousePressed', - type: 'fun', - params: [{ p: 'event', o: true }], - p5: true - }, - { - text: 'mouseReleased', - type: 'fun', - params: [{ p: 'event', o: true }], - p5: true - }, - { - text: 'mouseClicked', - type: 'fun', - params: [{ p: 'event', o: true }], - p5: true - }, - { - text: 'doubleClicked', - type: 'fun', - params: [{ p: 'event', o: true }], - p5: true - }, - { - text: 'mouseWheel', - type: 'fun', - params: [{ p: 'event', o: true }], - p5: true - }, - { text: 'requestPointerLock', type: 'fun', p5: true }, - { text: 'exitPointerLock', type: 'fun', p5: true }, - { text: 'touches', type: 'var', params: [], p5: true }, - { - text: 'touchStarted', - type: 'fun', - params: [{ p: 'event', o: true }], - p5: true - }, - { - text: 'touchMoved', - type: 'fun', - params: [{ p: 'event', o: true }], - p5: true - }, - { - text: 'touchEnded', - type: 'fun', - params: [{ p: 'event', o: true }], - p5: true - }, - { - text: 'createImage', - type: 'fun', - params: [ - { p: 'width', o: false }, - { p: 'height', o: false } - ], - p5: true - }, - { text: 'saveCanvas', type: 'fun', p5: true }, - { - text: 'saveFrames', - type: 'fun', - params: [ - { p: 'filename', o: false }, - { p: 'extension', o: false }, - { p: 'duration', o: false }, - { p: 'framerate', o: false }, - { p: 'callback', o: true } - ], - p5: true - }, - { - text: 'loadImage', - type: 'fun', - params: [ - { p: 'path', o: false }, - { p: 'successCallback', o: true }, - { p: 'failureCallback', o: true } - ], - p5: true - }, - { - text: 'saveGif', - type: 'fun', - params: [ - { p: 'filename', o: false }, - { p: 'duration', o: false }, - { p: 'options', o: true } - ], - p5: true - }, - { text: 'image', type: 'fun', p5: true }, - { text: 'tint', type: 'fun', p5: true }, - { text: 'noTint', type: 'fun', p5: true }, - { - text: 'imageMode', - type: 'fun', - params: [{ p: 'mode', o: false }], - p5: true - }, - { text: 'pixels', type: 'var', params: [], p5: true }, - { text: 'blend', type: 'fun', p5: true }, - { text: 'copy', type: 'fun', p5: true }, - { text: 'filter', type: 'fun', p5: true }, - { text: 'get', type: 'fun', p5: true }, - { text: 'loadPixels', type: 'fun', p5: true }, - { - text: 'set', - type: 'fun', - params: [ - { p: 'x', o: false }, - { p: 'y', o: false }, - { p: 'c', o: false } - ], - p5: true - }, - { - text: 'updatePixels', - type: 'fun', - params: [ - { p: 'x', o: true }, - { p: 'y', o: true }, - { p: 'w', o: true }, - { p: 'h', o: true } - ], - p5: true - }, - { - text: 'loadJSON', - type: 'fun', - params: [ - { p: 'path', o: false }, - { p: 'successCallback', o: true }, - { p: 'errorCallback', o: true } - ], - p5: true - }, - { - text: 'loadStrings', - type: 'fun', - params: [ - { p: 'path', o: false }, - { p: 'successCallback', o: true }, - { p: 'errorCallback', o: true } - ], - p5: true - }, - { - text: 'loadTable', - type: 'fun', - params: [ - { p: 'filename', o: false }, - { p: 'extension', o: true }, - { p: 'header', o: true }, - { p: 'callback', o: true }, - { p: 'errorCallback', o: true } - ], - p5: true - }, - { - text: 'loadXML', - type: 'fun', - params: [ - { p: 'path', o: false }, - { p: 'successCallback', o: true }, - { p: 'errorCallback', o: true } - ], - p5: true - }, - { - text: 'loadBytes', - type: 'fun', - params: [ - { p: 'file', o: false }, - { p: 'callback', o: true }, - { p: 'errorCallback', o: true } - ], - p5: true - }, - { text: 'httpGet', type: 'fun', p5: true }, - { text: 'httpPost', type: 'fun', p5: true }, - { text: 'httpDo', type: 'fun', p5: true }, - { - text: 'createWriter', - type: 'fun', - params: [ - { p: 'name', o: false }, - { p: 'extension', o: true } - ], - p5: true - }, - { - text: 'save', - type: 'fun', - params: [ - { p: 'objectOrFilename', o: true }, - { p: 'filename', o: true }, - { p: 'options', o: true } - ], - p5: true - }, - { - text: 'saveJSON', - type: 'fun', - params: [ - { p: 'json', o: false }, - { p: 'filename', o: false }, - { p: 'optimize', o: true } - ], - p5: true - }, - { - text: 'saveStrings', - type: 'fun', - params: [ - { p: 'list', o: false }, - { p: 'filename', o: false }, - { p: 'extension', o: true }, - { p: 'isCRLF', o: true } - ], - p5: true - }, - { - text: 'saveTable', - type: 'fun', - params: [ - { p: 'Table', o: false }, - { p: 'filename', o: false }, - { p: 'options', o: true } - ], - p5: true - }, - { text: 'abs', type: 'fun', params: [{ p: 'n', o: false }], p5: true }, - { text: 'ceil', type: 'fun', params: [{ p: 'n', o: false }], p5: true }, - { - text: 'constrain', - type: 'fun', - params: [ - { p: 'n', o: false }, - { p: 'low', o: false }, - { p: 'high', o: false } - ], - p5: true - }, - { text: 'dist', type: 'fun', p5: true }, - { text: 'exp', type: 'fun', params: [{ p: 'n', o: false }], p5: true }, - { text: 'floor', type: 'fun', params: [{ p: 'n', o: false }], p5: true }, - { - text: 'lerp', - type: 'fun', - params: [ - { p: 'start', o: false }, - { p: 'stop', o: false }, - { p: 'amt', o: false } - ], - p5: true - }, - { text: 'log', type: 'fun', params: [{ p: 'n', o: false }], p5: true }, - { - text: 'mag', - type: 'fun', - params: [ - { p: 'x', o: false }, - { p: 'y', o: false } - ], - p5: true - }, - { - text: 'map', - type: 'fun', - params: [ - { p: 'value', o: false }, - { p: 'start1', o: false }, - { p: 'stop1', o: false }, - { p: 'start2', o: false }, - { p: 'stop2', o: false }, - { p: 'withinBounds', o: true } - ], - p5: true - }, - { text: 'max', type: 'fun', p5: true }, - { text: 'min', type: 'fun', p5: true }, - { - text: 'norm', - type: 'fun', - params: [ - { p: 'value', o: false }, - { p: 'start', o: false }, - { p: 'stop', o: false } - ], - p5: true - }, - { - text: 'pow', - type: 'fun', - params: [ - { p: 'n', o: false }, - { p: 'e', o: false } - ], - p5: true - }, - { - text: 'round', - type: 'fun', - params: [ - { p: 'n', o: false }, - { p: 'decimals', o: true } - ], - p5: true - }, - { text: 'sq', type: 'fun', params: [{ p: 'n', o: false }], p5: true }, - { text: 'sqrt', type: 'fun', params: [{ p: 'n', o: false }], p5: true }, - { text: 'fract', type: 'fun', params: [{ p: 'n', o: false }], p5: true }, - { - text: 'createVector', - type: 'fun', - params: [ - { p: 'x', o: true }, - { p: 'y', o: true }, - { p: 'z', o: true } - ], - p5: true - }, - { - text: 'noise', - type: 'fun', - params: [ - { p: 'x', o: false }, - { p: 'y', o: true }, - { p: 'z', o: true } - ], - p5: true - }, - { - text: 'noiseDetail', - type: 'fun', - params: [ - { p: 'lod', o: false }, - { p: 'falloff', o: false } - ], - p5: true - }, - { - text: 'noiseSeed', - type: 'fun', - params: [{ p: 'seed', o: false }], - p5: true - }, - { - text: 'randomSeed', - type: 'fun', - params: [{ p: 'seed', o: false }], - p5: true - }, - { text: 'random', type: 'fun', p5: true }, - { - text: 'randomGaussian', - type: 'fun', - params: [ - { p: 'mean', o: true }, - { p: 'sd', o: true } - ], - p5: true - }, - { text: 'acos', type: 'fun', params: [{ p: 'value', o: false }], p5: true }, - { text: 'asin', type: 'fun', params: [{ p: 'value', o: false }], p5: true }, - { text: 'atan', type: 'fun', params: [{ p: 'value', o: false }], p5: true }, - { - text: 'atan2', - type: 'fun', - params: [ - { p: 'y', o: false }, - { p: 'x', o: false } - ], - p5: true - }, - { text: 'cos', type: 'fun', params: [{ p: 'angle', o: false }], p5: true }, - { text: 'sin', type: 'fun', params: [{ p: 'angle', o: false }], p5: true }, - { text: 'tan', type: 'fun', params: [{ p: 'angle', o: false }], p5: true }, - { - text: 'degrees', - type: 'fun', - params: [{ p: 'radians', o: false }], - p5: true - }, - { - text: 'radians', - type: 'fun', - params: [{ p: 'degrees', o: false }], - p5: true - }, - { text: 'angleMode', type: 'fun', p5: true }, - { text: 'textAlign', type: 'fun', p5: true }, - { text: 'textLeading', type: 'fun', p5: true }, - { text: 'textSize', type: 'fun', p5: true }, - { text: 'textStyle', type: 'fun', p5: true }, - { - text: 'textWidth', - type: 'fun', - params: [{ p: 'str', o: false }], - p5: true - }, - { text: 'textAscent', type: 'fun', p5: true }, - { text: 'textDescent', type: 'fun', p5: true }, - { - text: 'textWrap', - type: 'fun', - params: [{ p: 'style', o: false }], - p5: true - }, - { - text: 'loadFont', - type: 'fun', - params: [ - { p: 'path', o: false }, - { p: 'successCallback', o: true }, - { p: 'failureCallback', o: true } - ], - p5: true - }, - { - text: 'text', - type: 'fun', - params: [ - { p: 'str', o: false }, - { p: 'x', o: false }, - { p: 'y', o: false }, - { p: 'maxWidth', o: true }, - { p: 'maxHeight', o: true } - ], - p5: true - }, - { text: 'textFont', type: 'fun', p5: true }, - { - text: 'append', - type: 'fun', - params: [ - { p: 'array', o: false }, - { p: 'value', o: false } - ], - p5: true - }, - { text: 'arrayCopy', type: 'fun', p5: true }, - { - text: 'concat', - type: 'fun', - params: [ - { p: 'a', o: false }, - { p: 'b', o: false } - ], - p5: true - }, - { text: 'reverse', type: 'fun', params: [{ p: 'list', o: false }], p5: true }, - { text: 'shorten', type: 'fun', params: [{ p: 'list', o: false }], p5: true }, - { - text: 'shuffle', - type: 'fun', - params: [ - { p: 'array', o: false }, - { p: 'bool', o: true } - ], - p5: true - }, - { - text: 'sort', - type: 'fun', - params: [ - { p: 'list', o: false }, - { p: 'count', o: true } - ], - p5: true - }, - { - text: 'splice', - type: 'fun', - params: [ - { p: 'list', o: false }, - { p: 'value', o: false }, - { p: 'position', o: false } - ], - p5: true - }, - { - text: 'subset', - type: 'fun', - params: [ - { p: 'list', o: false }, - { p: 'start', o: false }, - { p: 'count', o: true } - ], - p5: true - }, - { text: 'float', type: 'fun', p5: true }, - { text: 'int', type: 'fun', p5: true }, - { text: 'str', type: 'fun', params: [{ p: 'n', o: false }], p5: true }, - { text: 'boolean', type: 'fun', p5: true }, - { text: 'byte', type: 'fun', p5: true }, - { text: 'char', type: 'fun', p5: true }, - { text: 'unchar', type: 'fun', p5: true }, - { text: 'hex', type: 'fun', p5: true }, - { text: 'unhex', type: 'fun', p5: true }, - { - text: 'join', - type: 'fun', - params: [ - { p: 'list', o: false }, - { p: 'separator', o: false } - ], - p5: true - }, - { - text: 'match', - type: 'fun', - params: [ - { p: 'str', o: false }, - { p: 'regexp', o: false } - ], - p5: true - }, - { - text: 'matchAll', - type: 'fun', - params: [ - { p: 'str', o: false }, - { p: 'regexp', o: false } - ], - p5: true - }, - { text: 'nf', type: 'fun', p5: true }, - { text: 'nfc', type: 'fun', p5: true }, - { text: 'nfp', type: 'fun', p5: true }, - { text: 'nfs', type: 'fun', p5: true }, - { - text: 'split', - type: 'fun', - params: [ - { p: 'value', o: false }, - { p: 'delim', o: false } - ], - p5: true - }, - { - text: 'splitTokens', - type: 'fun', - params: [ - { p: 'value', o: false }, - { p: 'delim', o: true } - ], - p5: true - }, - { text: 'trim', type: 'fun', p5: true }, - { text: 'day', type: 'fun', p5: true }, - { text: 'hour', type: 'fun', p5: true }, - { text: 'minute', type: 'fun', p5: true }, - { text: 'millis', type: 'fun', p5: true }, - { text: 'month', type: 'fun', p5: true }, - { text: 'second', type: 'fun', p5: true }, - { text: 'year', type: 'fun', p5: true }, - { text: 'beginGeometry', type: 'fun', p5: true }, - { text: 'endGeometry', type: 'fun', p5: true }, - { - text: 'buildGeometry', - type: 'fun', - params: [{ p: 'callback', o: false }], - p5: true - }, - { - text: 'freeGeometry', - type: 'fun', - params: [{ p: 'geometry', o: false }], - p5: true - }, - { - text: 'plane', - type: 'fun', - params: [ - { p: 'width', o: true }, - { p: 'height', o: true }, - { p: 'detailX', o: true }, - { p: 'detailY', o: true } - ], - p5: true - }, - { - text: 'box', - type: 'fun', - params: [ - { p: 'width', o: true }, - { p: 'height', o: true }, - { p: 'depth', o: true }, - { p: 'detailX', o: true }, - { p: 'detailY', o: true } - ], - p5: true - }, - { - text: 'sphere', - type: 'fun', - params: [ - { p: 'radius', o: true }, - { p: 'detailX', o: true }, - { p: 'detailY', o: true } - ], - p5: true - }, - { - text: 'cylinder', - type: 'fun', - params: [ - { p: 'radius', o: true }, - { p: 'height', o: true }, - { p: 'detailX', o: true }, - { p: 'detailY', o: true }, - { p: 'bottomCap', o: true }, - { p: 'topCap', o: true } - ], - p5: true - }, - { - text: 'cone', - type: 'fun', - params: [ - { p: 'radius', o: true }, - { p: 'height', o: true }, - { p: 'detailX', o: true }, - { p: 'detailY', o: true }, - { p: 'cap', o: true } - ], - p5: true - }, - { - text: 'ellipsoid', - type: 'fun', - params: [ - { p: 'radiusX', o: true }, - { p: 'radiusY', o: true }, - { p: 'radiusZ', o: true }, - { p: 'detailX', o: true }, - { p: 'detailY', o: true } - ], - p5: true - }, - { - text: 'torus', - type: 'fun', - params: [ - { p: 'radius', o: true }, - { p: 'tubeRadius', o: true }, - { p: 'detailX', o: true }, - { p: 'detailY', o: true } - ], - p5: true - }, - { - text: 'orbitControl', - type: 'fun', - params: [ - { p: 'sensitivityX', o: true }, - { p: 'sensitivityY', o: true }, - { p: 'sensitivityZ', o: true }, - { p: 'options', o: true } - ], - p5: true - }, - { text: 'debugMode', type: 'fun', p5: true }, - { text: 'noDebugMode', type: 'fun', p5: true }, - { text: 'ambientLight', type: 'fun', p5: true }, - { text: 'specularColor', type: 'fun', p5: true }, - { text: 'directionalLight', type: 'fun', p5: true }, - { text: 'pointLight', type: 'fun', p5: true }, - { - text: 'imageLight', - type: 'fun', - params: [{ p: 'img', o: false }], - p5: true - }, - { text: 'panorama', type: 'fun', params: [{ p: 'img', o: false }], p5: true }, - { text: 'lights', type: 'fun', p5: true }, - { - text: 'lightFalloff', - type: 'fun', - params: [ - { p: 'constant', o: false }, - { p: 'linear', o: false }, - { p: 'quadratic', o: false } - ], - p5: true - }, - { text: 'spotLight', type: 'fun', p5: true }, - { text: 'noLights', type: 'fun', p5: true }, - { text: 'loadModel', type: 'fun', p5: true }, - { text: 'model', type: 'fun', params: [{ p: 'model', o: false }], p5: true }, - { - text: 'loadShader', - type: 'fun', - params: [ - { p: 'vertFilename', o: false }, - { p: 'fragFilename', o: false }, - { p: 'successCallback', o: true }, - { p: 'failureCallback', o: true } - ], - p5: true - }, - { - text: 'createShader', - type: 'fun', - params: [ - { p: 'vertSrc', o: false }, - { p: 'fragSrc', o: false } - ], - p5: true - }, - { - text: 'createFilterShader', - type: 'fun', - params: [{ p: 'fragSrc', o: false }], - p5: true - }, - { text: 'shader', type: 'fun', params: [{ p: 's', o: false }], p5: true }, - { text: 'resetShader', type: 'fun', p5: true }, - { text: 'texture', type: 'fun', params: [{ p: 'tex', o: false }], p5: true }, - { - text: 'textureMode', - type: 'fun', - params: [{ p: 'mode', o: false }], - p5: true - }, - { - text: 'textureWrap', - type: 'fun', - params: [ - { p: 'wrapX', o: false }, - { p: 'wrapY', o: true } - ], - p5: true - }, - { text: 'normalMaterial', type: 'fun', p5: true }, - { text: 'ambientMaterial', type: 'fun', p5: true }, - { text: 'emissiveMaterial', type: 'fun', p5: true }, - { text: 'specularMaterial', type: 'fun', p5: true }, - { - text: 'shininess', - type: 'fun', - params: [{ p: 'shine', o: false }], - p5: true - }, - { - text: 'metalness', - type: 'fun', - params: [{ p: 'metallic', o: false }], - p5: true - }, - { - text: 'camera', - type: 'fun', - params: [ - { p: 'x', o: true }, - { p: 'y', o: true }, - { p: 'z', o: true }, - { p: 'centerX', o: true }, - { p: 'centerY', o: true }, - { p: 'centerZ', o: true }, - { p: 'upX', o: true }, - { p: 'upY', o: true }, - { p: 'upZ', o: true } - ], - p5: true - }, - { - text: 'perspective', - type: 'fun', - params: [ - { p: 'fovy', o: true }, - { p: 'aspect', o: true }, - { p: 'near', o: true }, - { p: 'far', o: true } - ], - p5: true - }, - { text: 'linePerspective', type: 'fun', p5: true }, - { - text: 'ortho', - type: 'fun', - params: [ - { p: 'left', o: true }, - { p: 'right', o: true }, - { p: 'bottom', o: true }, - { p: 'top', o: true }, - { p: 'near', o: true }, - { p: 'far', o: true } - ], - p5: true - }, - { - text: 'frustum', - type: 'fun', - params: [ - { p: 'left', o: true }, - { p: 'right', o: true }, - { p: 'bottom', o: true }, - { p: 'top', o: true }, - { p: 'near', o: true }, - { p: 'far', o: true } - ], - p5: true - }, - { text: 'createCamera', type: 'fun', p5: true }, - { - text: 'setCamera', - type: 'fun', - params: [{ p: 'cam', o: false }], - p5: true - }, - { text: 'setAttributes', type: 'fun', p5: true }, - { text: 'getAudioContext', type: 'fun', p5: true }, - { - text: 'userStartAudio', - type: 'fun', - params: [ - { p: 'elements', o: true }, - { p: 'callback', o: true } - ], - p5: true - }, - { text: 'getOutputVolume', type: 'fun', p5: true }, - { - text: 'outputVolume', - type: 'fun', - params: [ - { p: 'volume', o: false }, - { p: 'rampTime', o: true }, - { p: 'timeFromNow', o: true } - ], - p5: true - }, - { text: 'soundOut', type: 'var', params: [], p5: true }, - { text: 'sampleRate', type: 'fun', p5: true }, - { - text: 'freqToMidi', - type: 'fun', - params: [{ p: 'frequency', o: false }], - p5: true - }, - { - text: 'midiToFreq', - type: 'fun', - params: [{ p: 'midiNote', o: false }], - p5: true - }, - { - text: 'soundFormats', - type: 'fun', - params: [{ p: 'formats', o: true }], - p5: true - }, - { - text: 'saveSound', - type: 'fun', - params: [ - { p: 'soundFile', o: false }, - { p: 'fileName', o: false } - ], - p5: true - }, - { - text: 'loadSound', - type: 'fun', - params: [ - { p: 'path', o: false }, - { p: 'successCallback', o: true }, - { p: 'errorCallback', o: true }, - { p: 'whileLoading', o: true } - ], - p5: true - }, - { - text: 'createConvolver', - type: 'fun', - params: [ - { p: 'path', o: false }, - { p: 'callback', o: true }, - { p: 'errorCallback', o: true } - ], - p5: true - }, - { - text: 'setBPM', - type: 'fun', - params: [ - { p: 'BPM', o: false }, - { p: 'rampTime', o: false } - ], - p5: true - }, - { text: 'true', type: 'boolean', p5: 'boolean' }, - { text: 'false', type: 'boolean', p5: 'boolean' }, - { text: 'await', type: 'keyword', p5: false }, - { text: 'break', type: 'keyword', p5: false }, - { text: 'case', type: 'keyword', p5: false }, - { text: 'catch', type: 'keyword', p5: false }, - { text: 'class', type: 'keyword', p5: 'class' }, - { text: 'const', type: 'keyword', p5: 'const' }, - { text: 'continue', type: 'keyword', p5: false }, - { text: 'debugger', type: 'keyword', p5: false }, - { text: 'default', type: 'keyword', p5: false }, - { text: 'delete', type: 'keyword', p5: false }, - { text: 'do', type: 'keyword', p5: false }, - { text: 'else', type: 'keyword', p5: 'if-else' }, - { text: 'export', type: 'keyword', p5: false }, - { text: 'extends', type: 'keyword', p5: false }, - { text: 'finally', type: 'keyword', p5: false }, - { text: 'for', type: 'keyword', p5: 'for' }, - { text: 'function', type: 'keyword', p5: 'function' }, - { text: 'if', type: 'keyword', p5: 'if-else' }, - { text: 'import', type: 'keyword', p5: false }, - { text: 'in', type: 'keyword', p5: false }, - { text: 'instanceof', type: 'keyword', p5: false }, - { text: 'new', type: 'keyword', p5: false }, - { text: 'return', type: 'keyword', p5: 'return' }, - { text: 'super', type: 'keyword', p5: false }, - { text: 'switch', type: 'keyword', p5: false }, - { text: 'this', type: 'keyword', p5: false }, - { text: 'throw', type: 'keyword', p5: false }, - { text: 'try', type: 'keyword', p5: false }, - { text: 'typeof', type: 'keyword', p5: false }, - { text: 'var', type: 'keyword', p5: false }, - { text: 'void', type: 'keyword', p5: false }, - { text: 'while', type: 'keyword', p5: 'while' }, - { text: 'with', type: 'keyword', p5: false }, - { text: 'yield', type: 'keyword', p5: false }, - { text: 'let', type: 'keyword', p5: 'let' }, - { text: 'Array', type: 'obj', p5: false }, - { text: 'Boolean', type: 'obj', p5: false }, - { text: 'Date', type: 'obj', p5: false }, - { text: 'Error', type: 'obj', p5: false }, - { text: 'Function', type: 'obj', p5: false }, - { text: 'JSON', type: 'obj', p5: 'JSON' }, - { text: 'Math', type: 'obj', p5: false }, - { text: 'Number', type: 'obj', p5: false }, - { text: 'Object', type: 'obj', p5: false }, - { text: 'RegExp', type: 'obj', p5: false }, - { text: 'String', type: 'obj', p5: false }, - { text: 'Promise', type: 'obj', p5: false }, - { text: 'Set', type: 'obj', p5: false }, - { text: 'Map', type: 'obj', p5: false }, - { text: 'Symbol', type: 'obj', p5: false }, - { text: 'WeakMap', type: 'obj', p5: false }, - { text: 'WeakSet', type: 'obj', p5: false }, - { text: 'ArrayBuffer', type: 'obj', p5: false }, - { text: 'DataView', type: 'obj', p5: false }, - { text: 'Int32Array', type: 'obj', p5: false }, - { text: 'Uint32Array', type: 'obj', p5: false }, - { text: 'Float32Array', type: 'obj', p5: false }, - { text: 'window', type: 'obj', p5: false }, - { text: 'document', type: 'obj', p5: false }, - { text: 'navigator', type: 'obj', p5: false }, - { text: 'console', type: 'obj', p5: 'console' }, - { text: 'localStorage', type: 'obj', p5: false }, - { text: 'sessionStorage', type: 'obj', p5: false }, - { text: 'history', type: 'obj', p5: false }, - { text: 'location', type: 'obj', p5: false } -]; +exports.p5Hinter = [{"label":"describe","type":"method","kindLabel":"fun","params":[{"p":"text","o":false},{"p":"display","o":true}],"preview":"describe(text, [display])","p5DocPath":"describe"},{"label":"describeElement","type":"method","kindLabel":"fun","params":[{"p":"name","o":false},{"p":"text","o":false},{"p":"display","o":true}],"preview":"describeElement(name, text, [display])","p5DocPath":"describeElement"},{"label":"textOutput","type":"method","kindLabel":"fun","params":[{"p":"display","o":true}],"preview":"textOutput([display])","p5DocPath":"textOutput"},{"label":"gridOutput","type":"method","kindLabel":"fun","params":[{"p":"display","o":true}],"preview":"gridOutput([display])","p5DocPath":"gridOutput"},{"label":"alpha","type":"method","kindLabel":"fun","params":[{"p":"color","o":false}],"preview":"alpha(color)","p5DocPath":"alpha"},{"label":"blue","type":"method","kindLabel":"fun","params":[{"p":"color","o":false}],"preview":"blue(color)","p5DocPath":"blue"},{"label":"brightness","type":"method","kindLabel":"fun","params":[{"p":"color","o":false}],"preview":"brightness(color)","p5DocPath":"brightness"},{"label":"color","type":"method","kindLabel":"fun","preview":"color()","p5DocPath":"color"},{"label":"green","type":"method","kindLabel":"fun","params":[{"p":"color","o":false}],"preview":"green(color)","p5DocPath":"green"},{"label":"hue","type":"method","kindLabel":"fun","params":[{"p":"color","o":false}],"preview":"hue(color)","p5DocPath":"hue"},{"label":"lerpColor","type":"method","kindLabel":"fun","params":[{"p":"c1","o":false},{"p":"c2","o":false},{"p":"amt","o":false}],"preview":"lerpColor(c1, c2, amt)","p5DocPath":"lerpColor"},{"label":"paletteLerp","type":"method","kindLabel":"fun","params":[{"p":"colors_stops","o":false},{"p":"amt","o":false}],"preview":"paletteLerp(colors_stops, amt)","p5DocPath":"paletteLerp"},{"label":"lightness","type":"method","kindLabel":"fun","params":[{"p":"color","o":false}],"preview":"lightness(color)","p5DocPath":"lightness"},{"label":"red","type":"method","kindLabel":"fun","params":[{"p":"color","o":false}],"preview":"red(color)","p5DocPath":"red"},{"label":"saturation","type":"method","kindLabel":"fun","params":[{"p":"color","o":false}],"preview":"saturation(color)","p5DocPath":"saturation"},{"label":"beginClip","type":"method","kindLabel":"fun","params":[{"p":"options","o":true}],"preview":"beginClip([options])","p5DocPath":"beginClip"},{"label":"endClip","type":"method","kindLabel":"fun","preview":"endClip()","p5DocPath":"endClip"},{"label":"clip","type":"method","kindLabel":"fun","params":[{"p":"callback","o":false},{"p":"options","o":true}],"preview":"clip(callback, [options])","p5DocPath":"clip"},{"label":"background","type":"method","kindLabel":"fun","preview":"background()","p5DocPath":"background"},{"label":"clear","type":"method","kindLabel":"fun","params":[{"p":"r","o":true},{"p":"g","o":true},{"p":"b","o":true},{"p":"a","o":true}],"preview":"clear([r], [g], [b], [a])","p5DocPath":"clear"},{"label":"colorMode","type":"method","kindLabel":"fun","preview":"colorMode()","p5DocPath":"colorMode"},{"label":"fill","type":"method","kindLabel":"fun","preview":"fill()","p5DocPath":"fill"},{"label":"noFill","type":"method","kindLabel":"fun","preview":"noFill()","p5DocPath":"noFill"},{"label":"noStroke","type":"method","kindLabel":"fun","preview":"noStroke()","p5DocPath":"noStroke"},{"label":"stroke","type":"method","kindLabel":"fun","preview":"stroke()","p5DocPath":"stroke"},{"label":"erase","type":"method","kindLabel":"fun","params":[{"p":"strengthFill","o":true},{"p":"strengthStroke","o":true}],"preview":"erase([strengthFill], [strengthStroke])","p5DocPath":"erase"},{"label":"noErase","type":"method","kindLabel":"fun","preview":"noErase()","p5DocPath":"noErase"},{"label":"arc","type":"method","kindLabel":"fun","params":[{"p":"x","o":false},{"p":"y","o":false},{"p":"w","o":false},{"p":"h","o":false},{"p":"start","o":false},{"p":"stop","o":false},{"p":"mode","o":true},{"p":"detail","o":true}],"preview":"arc(x, y, w, h, start, stop, [mode], [detail])","p5DocPath":"arc"},{"label":"ellipse","type":"method","kindLabel":"fun","preview":"ellipse()","p5DocPath":"ellipse"},{"label":"circle","type":"method","kindLabel":"fun","params":[{"p":"x","o":false},{"p":"y","o":false},{"p":"d","o":false}],"preview":"circle(x, y, d)","p5DocPath":"circle"},{"label":"line","type":"method","kindLabel":"fun","preview":"line()","p5DocPath":"line"},{"label":"point","type":"method","kindLabel":"fun","preview":"point()","p5DocPath":"point"},{"label":"quad","type":"method","kindLabel":"fun","preview":"quad()","p5DocPath":"quad"},{"label":"rect","type":"method","kindLabel":"fun","preview":"rect()","p5DocPath":"rect"},{"label":"square","type":"method","kindLabel":"fun","params":[{"p":"x","o":false},{"p":"y","o":false},{"p":"s","o":false},{"p":"tl","o":true},{"p":"tr","o":true},{"p":"br","o":true},{"p":"bl","o":true}],"preview":"square(x, y, s, [tl], [tr], [br], [bl])","p5DocPath":"square"},{"label":"triangle","type":"method","kindLabel":"fun","params":[{"p":"x1","o":false},{"p":"y1","o":false},{"p":"x2","o":false},{"p":"y2","o":false},{"p":"x3","o":false},{"p":"y3","o":false}],"preview":"triangle(x1, y1, x2, y2, x3, y3)","p5DocPath":"triangle"},{"label":"ellipseMode","type":"method","kindLabel":"fun","params":[{"p":"mode","o":false}],"preview":"ellipseMode(mode)","p5DocPath":"ellipseMode"},{"label":"noSmooth","type":"method","kindLabel":"fun","preview":"noSmooth()","p5DocPath":"noSmooth"},{"label":"rectMode","type":"method","kindLabel":"fun","params":[{"p":"mode","o":false}],"preview":"rectMode(mode)","p5DocPath":"rectMode"},{"label":"smooth","type":"method","kindLabel":"fun","preview":"smooth()","p5DocPath":"smooth"},{"label":"strokeCap","type":"method","kindLabel":"fun","params":[{"p":"cap","o":false}],"preview":"strokeCap(cap)","p5DocPath":"strokeCap"},{"label":"strokeJoin","type":"method","kindLabel":"fun","params":[{"p":"join","o":false}],"preview":"strokeJoin(join)","p5DocPath":"strokeJoin"},{"label":"strokeWeight","type":"method","kindLabel":"fun","params":[{"p":"weight","o":false}],"preview":"strokeWeight(weight)","p5DocPath":"strokeWeight"},{"label":"bezier","type":"method","kindLabel":"fun","preview":"bezier()","p5DocPath":"bezier"},{"label":"bezierDetail","type":"method","kindLabel":"fun","params":[{"p":"detail","o":false}],"preview":"bezierDetail(detail)","p5DocPath":"bezierDetail"},{"label":"bezierPoint","type":"method","kindLabel":"fun","params":[{"p":"a","o":false},{"p":"b","o":false},{"p":"c","o":false},{"p":"d","o":false},{"p":"t","o":false}],"preview":"bezierPoint(a, b, c, d, t)","p5DocPath":"bezierPoint"},{"label":"bezierTangent","type":"method","kindLabel":"fun","params":[{"p":"a","o":false},{"p":"b","o":false},{"p":"c","o":false},{"p":"d","o":false},{"p":"t","o":false}],"preview":"bezierTangent(a, b, c, d, t)","p5DocPath":"bezierTangent"},{"label":"curve","type":"method","kindLabel":"fun","preview":"curve()","p5DocPath":"curve"},{"label":"curveDetail","type":"method","kindLabel":"fun","params":[{"p":"resolution","o":false}],"preview":"curveDetail(resolution)","p5DocPath":"curveDetail"},{"label":"curveTightness","type":"method","kindLabel":"fun","params":[{"p":"amount","o":false}],"preview":"curveTightness(amount)","p5DocPath":"curveTightness"},{"label":"curvePoint","type":"method","kindLabel":"fun","params":[{"p":"a","o":false},{"p":"b","o":false},{"p":"c","o":false},{"p":"d","o":false},{"p":"t","o":false}],"preview":"curvePoint(a, b, c, d, t)","p5DocPath":"curvePoint"},{"label":"curveTangent","type":"method","kindLabel":"fun","params":[{"p":"a","o":false},{"p":"b","o":false},{"p":"c","o":false},{"p":"d","o":false},{"p":"t","o":false}],"preview":"curveTangent(a, b, c, d, t)","p5DocPath":"curveTangent"},{"label":"beginContour","type":"method","kindLabel":"fun","preview":"beginContour()","p5DocPath":"beginContour"},{"label":"beginShape","type":"method","kindLabel":"fun","params":[{"p":"kind","o":true}],"preview":"beginShape([kind])","p5DocPath":"beginShape"},{"label":"bezierVertex","type":"method","kindLabel":"fun","preview":"bezierVertex()","p5DocPath":"bezierVertex"},{"label":"curveVertex","type":"method","kindLabel":"fun","preview":"curveVertex()","p5DocPath":"curveVertex"},{"label":"endContour","type":"method","kindLabel":"fun","preview":"endContour()","p5DocPath":"endContour"},{"label":"endShape","type":"method","kindLabel":"fun","params":[{"p":"mode","o":true},{"p":"count","o":true}],"preview":"endShape([mode], [count])","p5DocPath":"endShape"},{"label":"quadraticVertex","type":"method","kindLabel":"fun","preview":"quadraticVertex()","p5DocPath":"quadraticVertex"},{"label":"vertex","type":"method","kindLabel":"fun","preview":"vertex()","p5DocPath":"vertex"},{"label":"normal","type":"method","kindLabel":"fun","preview":"normal()","p5DocPath":"normal"},{"label":"VERSION","type":"constant","kindLabel":"const","params":[],"preview":"VERSION","p5DocPath":"VERSION"},{"label":"P2D","type":"constant","kindLabel":"const","params":[],"preview":"P2D","p5DocPath":"P2D"},{"label":"WEBGL","type":"constant","kindLabel":"const","params":[],"preview":"WEBGL","p5DocPath":"WEBGL"},{"label":"WEBGL2","type":"constant","kindLabel":"const","params":[],"preview":"WEBGL2","p5DocPath":"WEBGL2"},{"label":"ARROW","type":"constant","kindLabel":"const","params":[],"preview":"ARROW","p5DocPath":"ARROW"},{"label":"CROSS","type":"constant","kindLabel":"const","params":[],"preview":"CROSS","p5DocPath":"CROSS"},{"label":"HAND","type":"constant","kindLabel":"const","params":[],"preview":"HAND","p5DocPath":"HAND"},{"label":"MOVE","type":"constant","kindLabel":"const","params":[],"preview":"MOVE","p5DocPath":"MOVE"},{"label":"TEXT","type":"constant","kindLabel":"const","params":[],"preview":"TEXT","p5DocPath":"TEXT"},{"label":"WAIT","type":"constant","kindLabel":"const","params":[],"preview":"WAIT","p5DocPath":"WAIT"},{"label":"HALF_PI","type":"constant","kindLabel":"const","params":[],"preview":"HALF_PI","p5DocPath":"HALF_PI"},{"label":"PI","type":"constant","kindLabel":"const","params":[],"preview":"PI","p5DocPath":"PI"},{"label":"QUARTER_PI","type":"constant","kindLabel":"const","params":[],"preview":"QUARTER_PI","p5DocPath":"QUARTER_PI"},{"label":"TAU","type":"constant","kindLabel":"const","params":[],"preview":"TAU","p5DocPath":"TAU"},{"label":"TWO_PI","type":"constant","kindLabel":"const","params":[],"preview":"TWO_PI","p5DocPath":"TWO_PI"},{"label":"DEGREES","type":"constant","kindLabel":"const","params":[],"preview":"DEGREES","p5DocPath":"DEGREES"},{"label":"RADIANS","type":"constant","kindLabel":"const","params":[],"preview":"RADIANS","p5DocPath":"RADIANS"},{"label":"CORNER","type":"constant","kindLabel":"const","params":[],"preview":"CORNER","p5DocPath":"CORNER"},{"label":"CORNERS","type":"constant","kindLabel":"const","params":[],"preview":"CORNERS","p5DocPath":"CORNERS"},{"label":"RADIUS","type":"constant","kindLabel":"const","params":[],"preview":"RADIUS","p5DocPath":"RADIUS"},{"label":"RIGHT","type":"constant","kindLabel":"const","params":[],"preview":"RIGHT","p5DocPath":"RIGHT"},{"label":"LEFT","type":"constant","kindLabel":"const","params":[],"preview":"LEFT","p5DocPath":"LEFT"},{"label":"CENTER","type":"constant","kindLabel":"const","params":[],"preview":"CENTER","p5DocPath":"CENTER"},{"label":"TOP","type":"constant","kindLabel":"const","params":[],"preview":"TOP","p5DocPath":"TOP"},{"label":"BOTTOM","type":"constant","kindLabel":"const","params":[],"preview":"BOTTOM","p5DocPath":"BOTTOM"},{"label":"BASELINE","type":"constant","kindLabel":"const","params":[],"preview":"BASELINE","p5DocPath":"BASELINE"},{"label":"POINTS","type":"constant","kindLabel":"const","params":[],"preview":"POINTS","p5DocPath":"POINTS"},{"label":"LINES","type":"constant","kindLabel":"const","params":[],"preview":"LINES","p5DocPath":"LINES"},{"label":"LINE_STRIP","type":"constant","kindLabel":"const","params":[],"preview":"LINE_STRIP","p5DocPath":"LINE_STRIP"},{"label":"LINE_LOOP","type":"constant","kindLabel":"const","params":[],"preview":"LINE_LOOP","p5DocPath":"LINE_LOOP"},{"label":"TRIANGLES","type":"constant","kindLabel":"const","params":[],"preview":"TRIANGLES","p5DocPath":"TRIANGLES"},{"label":"TRIANGLE_FAN","type":"constant","kindLabel":"const","params":[],"preview":"TRIANGLE_FAN","p5DocPath":"TRIANGLE_FAN"},{"label":"TRIANGLE_STRIP","type":"constant","kindLabel":"const","params":[],"preview":"TRIANGLE_STRIP","p5DocPath":"TRIANGLE_STRIP"},{"label":"QUADS","type":"constant","kindLabel":"const","params":[],"preview":"QUADS","p5DocPath":"QUADS"},{"label":"QUAD_STRIP","type":"constant","kindLabel":"const","params":[],"preview":"QUAD_STRIP","p5DocPath":"QUAD_STRIP"},{"label":"TESS","type":"constant","kindLabel":"const","params":[],"preview":"TESS","p5DocPath":"TESS"},{"label":"CLOSE","type":"constant","kindLabel":"const","params":[],"preview":"CLOSE","p5DocPath":"CLOSE"},{"label":"OPEN","type":"constant","kindLabel":"const","params":[],"preview":"OPEN","p5DocPath":"OPEN"},{"label":"CHORD","type":"constant","kindLabel":"const","params":[],"preview":"CHORD","p5DocPath":"CHORD"},{"label":"PIE","type":"constant","kindLabel":"const","params":[],"preview":"PIE","p5DocPath":"PIE"},{"label":"PROJECT","type":"constant","kindLabel":"const","params":[],"preview":"PROJECT","p5DocPath":"PROJECT"},{"label":"SQUARE","type":"constant","kindLabel":"const","params":[],"preview":"SQUARE","p5DocPath":"SQUARE"},{"label":"ROUND","type":"constant","kindLabel":"const","params":[],"preview":"ROUND","p5DocPath":"ROUND"},{"label":"BEVEL","type":"constant","kindLabel":"const","params":[],"preview":"BEVEL","p5DocPath":"BEVEL"},{"label":"MITER","type":"constant","kindLabel":"const","params":[],"preview":"MITER","p5DocPath":"MITER"},{"label":"RGB","type":"constant","kindLabel":"const","params":[],"preview":"RGB","p5DocPath":"RGB"},{"label":"HSB","type":"constant","kindLabel":"const","params":[],"preview":"HSB","p5DocPath":"HSB"},{"label":"HSL","type":"constant","kindLabel":"const","params":[],"preview":"HSL","p5DocPath":"HSL"},{"label":"AUTO","type":"constant","kindLabel":"const","params":[],"preview":"AUTO","p5DocPath":"AUTO"},{"label":"ALT","type":"constant","kindLabel":"const","params":[],"preview":"ALT","p5DocPath":"ALT"},{"label":"BACKSPACE","type":"constant","kindLabel":"const","params":[],"preview":"BACKSPACE","p5DocPath":"BACKSPACE"},{"label":"CONTROL","type":"constant","kindLabel":"const","params":[],"preview":"CONTROL","p5DocPath":"CONTROL"},{"label":"DELETE","type":"constant","kindLabel":"const","params":[],"preview":"DELETE","p5DocPath":"DELETE"},{"label":"DOWN_ARROW","type":"constant","kindLabel":"const","params":[],"preview":"DOWN_ARROW","p5DocPath":"DOWN_ARROW"},{"label":"ENTER","type":"constant","kindLabel":"const","params":[],"preview":"ENTER","p5DocPath":"ENTER"},{"label":"ESCAPE","type":"constant","kindLabel":"const","params":[],"preview":"ESCAPE","p5DocPath":"ESCAPE"},{"label":"LEFT_ARROW","type":"constant","kindLabel":"const","params":[],"preview":"LEFT_ARROW","p5DocPath":"LEFT_ARROW"},{"label":"OPTION","type":"constant","kindLabel":"const","params":[],"preview":"OPTION","p5DocPath":"OPTION"},{"label":"RETURN","type":"constant","kindLabel":"const","params":[],"preview":"RETURN","p5DocPath":"RETURN"},{"label":"RIGHT_ARROW","type":"constant","kindLabel":"const","params":[],"preview":"RIGHT_ARROW","p5DocPath":"RIGHT_ARROW"},{"label":"SHIFT","type":"constant","kindLabel":"const","params":[],"preview":"SHIFT","p5DocPath":"SHIFT"},{"label":"TAB","type":"constant","kindLabel":"const","params":[],"preview":"TAB","p5DocPath":"TAB"},{"label":"UP_ARROW","type":"constant","kindLabel":"const","params":[],"preview":"UP_ARROW","p5DocPath":"UP_ARROW"},{"label":"BLEND","type":"constant","kindLabel":"const","params":[],"preview":"BLEND","p5DocPath":"BLEND"},{"label":"REMOVE","type":"constant","kindLabel":"const","params":[],"preview":"REMOVE","p5DocPath":"REMOVE"},{"label":"ADD","type":"constant","kindLabel":"const","params":[],"preview":"ADD","p5DocPath":"ADD"},{"label":"DARKEST","type":"constant","kindLabel":"const","params":[],"preview":"DARKEST","p5DocPath":"DARKEST"},{"label":"LIGHTEST","type":"constant","kindLabel":"const","params":[],"preview":"LIGHTEST","p5DocPath":"LIGHTEST"},{"label":"DIFFERENCE","type":"constant","kindLabel":"const","params":[],"preview":"DIFFERENCE","p5DocPath":"DIFFERENCE"},{"label":"SUBTRACT","type":"constant","kindLabel":"const","params":[],"preview":"SUBTRACT","p5DocPath":"SUBTRACT"},{"label":"EXCLUSION","type":"constant","kindLabel":"const","params":[],"preview":"EXCLUSION","p5DocPath":"EXCLUSION"},{"label":"MULTIPLY","type":"constant","kindLabel":"const","params":[],"preview":"MULTIPLY","p5DocPath":"MULTIPLY"},{"label":"SCREEN","type":"constant","kindLabel":"const","params":[],"preview":"SCREEN","p5DocPath":"SCREEN"},{"label":"REPLACE","type":"constant","kindLabel":"const","params":[],"preview":"REPLACE","p5DocPath":"REPLACE"},{"label":"OVERLAY","type":"constant","kindLabel":"const","params":[],"preview":"OVERLAY","p5DocPath":"OVERLAY"},{"label":"HARD_LIGHT","type":"constant","kindLabel":"const","params":[],"preview":"HARD_LIGHT","p5DocPath":"HARD_LIGHT"},{"label":"SOFT_LIGHT","type":"constant","kindLabel":"const","params":[],"preview":"SOFT_LIGHT","p5DocPath":"SOFT_LIGHT"},{"label":"DODGE","type":"constant","kindLabel":"const","params":[],"preview":"DODGE","p5DocPath":"DODGE"},{"label":"BURN","type":"constant","kindLabel":"const","params":[],"preview":"BURN","p5DocPath":"BURN"},{"label":"THRESHOLD","type":"constant","kindLabel":"const","params":[],"preview":"THRESHOLD","p5DocPath":"THRESHOLD"},{"label":"GRAY","type":"constant","kindLabel":"const","params":[],"preview":"GRAY","p5DocPath":"GRAY"},{"label":"OPAQUE","type":"constant","kindLabel":"const","params":[],"preview":"OPAQUE","p5DocPath":"OPAQUE"},{"label":"INVERT","type":"constant","kindLabel":"const","params":[],"preview":"INVERT","p5DocPath":"INVERT"},{"label":"POSTERIZE","type":"constant","kindLabel":"const","params":[],"preview":"POSTERIZE","p5DocPath":"POSTERIZE"},{"label":"DILATE","type":"constant","kindLabel":"const","params":[],"preview":"DILATE","p5DocPath":"DILATE"},{"label":"ERODE","type":"constant","kindLabel":"const","params":[],"preview":"ERODE","p5DocPath":"ERODE"},{"label":"BLUR","type":"constant","kindLabel":"const","params":[],"preview":"BLUR","p5DocPath":"BLUR"},{"label":"NORMAL","type":"constant","kindLabel":"const","params":[],"preview":"NORMAL","p5DocPath":"NORMAL"},{"label":"ITALIC","type":"constant","kindLabel":"const","params":[],"preview":"ITALIC","p5DocPath":"ITALIC"},{"label":"BOLD","type":"constant","kindLabel":"const","params":[],"preview":"BOLD","p5DocPath":"BOLD"},{"label":"BOLDITALIC","type":"constant","kindLabel":"const","params":[],"preview":"BOLDITALIC","p5DocPath":"BOLDITALIC"},{"label":"CHAR","type":"constant","kindLabel":"const","params":[],"preview":"CHAR","p5DocPath":"CHAR"},{"label":"WORD","type":"constant","kindLabel":"const","params":[],"preview":"WORD","p5DocPath":"WORD"},{"label":"LINEAR","type":"constant","kindLabel":"const","params":[],"preview":"LINEAR","p5DocPath":"LINEAR"},{"label":"QUADRATIC","type":"constant","kindLabel":"const","params":[],"preview":"QUADRATIC","p5DocPath":"QUADRATIC"},{"label":"BEZIER","type":"constant","kindLabel":"const","params":[],"preview":"BEZIER","p5DocPath":"BEZIER"},{"label":"CURVE","type":"constant","kindLabel":"const","params":[],"preview":"CURVE","p5DocPath":"CURVE"},{"label":"STROKE","type":"constant","kindLabel":"const","params":[],"preview":"STROKE","p5DocPath":"STROKE"},{"label":"FILL","type":"constant","kindLabel":"const","params":[],"preview":"FILL","p5DocPath":"FILL"},{"label":"TEXTURE","type":"constant","kindLabel":"const","params":[],"preview":"TEXTURE","p5DocPath":"TEXTURE"},{"label":"IMMEDIATE","type":"constant","kindLabel":"const","params":[],"preview":"IMMEDIATE","p5DocPath":"IMMEDIATE"},{"label":"IMAGE","type":"constant","kindLabel":"const","params":[],"preview":"IMAGE","p5DocPath":"IMAGE"},{"label":"NEAREST","type":"constant","kindLabel":"const","params":[],"preview":"NEAREST","p5DocPath":"NEAREST"},{"label":"REPEAT","type":"constant","kindLabel":"const","params":[],"preview":"REPEAT","p5DocPath":"REPEAT"},{"label":"CLAMP","type":"constant","kindLabel":"const","params":[],"preview":"CLAMP","p5DocPath":"CLAMP"},{"label":"MIRROR","type":"constant","kindLabel":"const","params":[],"preview":"MIRROR","p5DocPath":"MIRROR"},{"label":"FLAT","type":"constant","kindLabel":"const","params":[],"preview":"FLAT","p5DocPath":"FLAT"},{"label":"SMOOTH","type":"constant","kindLabel":"const","params":[],"preview":"SMOOTH","p5DocPath":"SMOOTH"},{"label":"LANDSCAPE","type":"constant","kindLabel":"const","params":[],"preview":"LANDSCAPE","p5DocPath":"LANDSCAPE"},{"label":"PORTRAIT","type":"constant","kindLabel":"const","params":[],"preview":"PORTRAIT","p5DocPath":"PORTRAIT"},{"label":"GRID","type":"constant","kindLabel":"const","params":[],"preview":"GRID","p5DocPath":"GRID"},{"label":"AXES","type":"constant","kindLabel":"const","params":[],"preview":"AXES","p5DocPath":"AXES"},{"label":"LABEL","type":"constant","kindLabel":"const","params":[],"preview":"LABEL","p5DocPath":"LABEL"},{"label":"FALLBACK","type":"constant","kindLabel":"const","params":[],"preview":"FALLBACK","p5DocPath":"FALLBACK"},{"label":"CONTAIN","type":"constant","kindLabel":"const","params":[],"preview":"CONTAIN","p5DocPath":"CONTAIN"},{"label":"COVER","type":"constant","kindLabel":"const","params":[],"preview":"COVER","p5DocPath":"COVER"},{"label":"UNSIGNED_BYTE","type":"constant","kindLabel":"const","params":[],"preview":"UNSIGNED_BYTE","p5DocPath":"UNSIGNED_BYTE"},{"label":"UNSIGNED_INT","type":"constant","kindLabel":"const","params":[],"preview":"UNSIGNED_INT","p5DocPath":"UNSIGNED_INT"},{"label":"FLOAT","type":"constant","kindLabel":"const","params":[],"preview":"FLOAT","p5DocPath":"FLOAT"},{"label":"HALF_FLOAT","type":"constant","kindLabel":"const","params":[],"preview":"HALF_FLOAT","p5DocPath":"HALF_FLOAT"},{"label":"RGBA","type":"constant","kindLabel":"const","params":[],"preview":"RGBA","p5DocPath":"RGBA"},{"label":"print","type":"method","kindLabel":"fun","params":[{"p":"contents","o":false}],"preview":"print(contents)","p5DocPath":"print"},{"label":"frameCount","type":"variable","kindLabel":"var","params":[],"preview":"frameCount","p5DocPath":"frameCount"},{"label":"deltaTime","type":"variable","kindLabel":"var","params":[],"preview":"deltaTime","p5DocPath":"deltaTime"},{"label":"focused","type":"variable","kindLabel":"var","params":[],"preview":"focused","p5DocPath":"focused"},{"label":"cursor","type":"method","kindLabel":"fun","params":[{"p":"type","o":false},{"p":"x","o":true},{"p":"y","o":true}],"preview":"cursor(type, [x], [y])","p5DocPath":"cursor"},{"label":"frameRate","type":"method","kindLabel":"fun","preview":"frameRate()","p5DocPath":"frameRate"},{"label":"getTargetFrameRate","type":"method","kindLabel":"fun","preview":"getTargetFrameRate()","p5DocPath":"getTargetFrameRate"},{"label":"noCursor","type":"method","kindLabel":"fun","preview":"noCursor()","p5DocPath":"noCursor"},{"label":"webglVersion","type":"variable","kindLabel":"var","params":[],"preview":"webglVersion","p5DocPath":"webglVersion"},{"label":"displayWidth","type":"variable","kindLabel":"var","params":[],"preview":"displayWidth","p5DocPath":"displayWidth"},{"label":"displayHeight","type":"variable","kindLabel":"var","params":[],"preview":"displayHeight","p5DocPath":"displayHeight"},{"label":"windowWidth","type":"variable","kindLabel":"var","params":[],"preview":"windowWidth","p5DocPath":"windowWidth"},{"label":"windowHeight","type":"variable","kindLabel":"var","params":[],"preview":"windowHeight","p5DocPath":"windowHeight"},{"label":"windowResized","type":"method","kindLabel":"fun","params":[{"p":"event","o":true}],"preview":"windowResized([event])","p5DocPath":"windowResized"},{"label":"width","type":"variable","kindLabel":"var","params":[],"preview":"width","p5DocPath":"width"},{"label":"height","type":"variable","kindLabel":"var","params":[],"preview":"height","p5DocPath":"height"},{"label":"fullscreen","type":"method","kindLabel":"fun","params":[{"p":"val","o":true}],"preview":"fullscreen([val])","p5DocPath":"fullscreen"},{"label":"pixelDensity","type":"method","kindLabel":"fun","preview":"pixelDensity()","p5DocPath":"pixelDensity"},{"label":"displayDensity","type":"method","kindLabel":"fun","preview":"displayDensity()","p5DocPath":"displayDensity"},{"label":"getURL","type":"method","kindLabel":"fun","preview":"getURL()","p5DocPath":"getURL"},{"label":"getURLPath","type":"method","kindLabel":"fun","preview":"getURLPath()","p5DocPath":"getURLPath"},{"label":"getURLParams","type":"method","kindLabel":"fun","preview":"getURLParams()","p5DocPath":"getURLParams"},{"label":"preload","type":"method","kindLabel":"fun","preview":"preload()","p5DocPath":"preload"},{"label":"setup","type":"method","kindLabel":"fun","preview":"setup()","p5DocPath":"setup"},{"label":"draw","type":"method","kindLabel":"fun","preview":"draw()","p5DocPath":"draw"},{"label":"remove","type":"method","kindLabel":"fun","preview":"remove()","p5DocPath":"remove"},{"label":"disableFriendlyErrors","type":"variable","kindLabel":"var","params":[],"preview":"disableFriendlyErrors","p5DocPath":"disableFriendlyErrors"},{"label":"createCanvas","type":"method","kindLabel":"fun","preview":"createCanvas()","p5DocPath":"createCanvas"},{"label":"resizeCanvas","type":"method","kindLabel":"fun","params":[{"p":"width","o":false},{"p":"height","o":false},{"p":"noRedraw","o":true}],"preview":"resizeCanvas(width, height, [noRedraw])","p5DocPath":"resizeCanvas"},{"label":"noCanvas","type":"method","kindLabel":"fun","preview":"noCanvas()","p5DocPath":"noCanvas"},{"label":"createGraphics","type":"method","kindLabel":"fun","preview":"createGraphics()","p5DocPath":"createGraphics"},{"label":"createFramebuffer","type":"method","kindLabel":"fun","params":[{"p":"options","o":true}],"preview":"createFramebuffer([options])","p5DocPath":"createFramebuffer"},{"label":"clearDepth","type":"method","kindLabel":"fun","params":[{"p":"depth","o":true}],"preview":"clearDepth([depth])","p5DocPath":"clearDepth"},{"label":"blendMode","type":"method","kindLabel":"fun","params":[{"p":"mode","o":false}],"preview":"blendMode(mode)","p5DocPath":"blendMode"},{"label":"drawingContext","type":"variable","kindLabel":"var","params":[],"preview":"drawingContext","p5DocPath":"drawingContext"},{"label":"noLoop","type":"method","kindLabel":"fun","preview":"noLoop()","p5DocPath":"noLoop"},{"label":"loop","type":"method","kindLabel":"fun","preview":"loop()","p5DocPath":"loop"},{"label":"isLooping","type":"method","kindLabel":"fun","preview":"isLooping()","p5DocPath":"isLooping"},{"label":"push","type":"method","kindLabel":"fun","preview":"push()","p5DocPath":"push"},{"label":"pop","type":"method","kindLabel":"fun","preview":"pop()","p5DocPath":"pop"},{"label":"redraw","type":"method","kindLabel":"fun","params":[{"p":"n","o":true}],"preview":"redraw([n])","p5DocPath":"redraw"},{"label":"p5","type":"method","kindLabel":"fun","params":[{"p":"sketch","o":false},{"p":"node","o":false}],"preview":"p5(sketch, node)","p5DocPath":"p5"},{"label":"applyMatrix","type":"method","kindLabel":"fun","preview":"applyMatrix()","p5DocPath":"applyMatrix"},{"label":"resetMatrix","type":"method","kindLabel":"fun","preview":"resetMatrix()","p5DocPath":"resetMatrix"},{"label":"rotate","type":"method","kindLabel":"fun","params":[{"p":"angle","o":false},{"p":"axis","o":true}],"preview":"rotate(angle, [axis])","p5DocPath":"rotate"},{"label":"rotateX","type":"method","kindLabel":"fun","params":[{"p":"angle","o":false}],"preview":"rotateX(angle)","p5DocPath":"rotateX"},{"label":"rotateY","type":"method","kindLabel":"fun","params":[{"p":"angle","o":false}],"preview":"rotateY(angle)","p5DocPath":"rotateY"},{"label":"rotateZ","type":"method","kindLabel":"fun","params":[{"p":"angle","o":false}],"preview":"rotateZ(angle)","p5DocPath":"rotateZ"},{"label":"scale","type":"method","kindLabel":"fun","preview":"scale()","p5DocPath":"scale"},{"label":"shearX","type":"method","kindLabel":"fun","params":[{"p":"angle","o":false}],"preview":"shearX(angle)","p5DocPath":"shearX"},{"label":"shearY","type":"method","kindLabel":"fun","params":[{"p":"angle","o":false}],"preview":"shearY(angle)","p5DocPath":"shearY"},{"label":"translate","type":"method","kindLabel":"fun","preview":"translate()","p5DocPath":"translate"},{"label":"storeItem","type":"method","kindLabel":"fun","params":[{"p":"key","o":false},{"p":"value","o":false}],"preview":"storeItem(key, value)","p5DocPath":"storeItem"},{"label":"getItem","type":"method","kindLabel":"fun","params":[{"p":"key","o":false}],"preview":"getItem(key)","p5DocPath":"getItem"},{"label":"clearStorage","type":"method","kindLabel":"fun","preview":"clearStorage()","p5DocPath":"clearStorage"},{"label":"removeItem","type":"method","kindLabel":"fun","params":[{"p":"key","o":false}],"preview":"removeItem(key)","p5DocPath":"removeItem"},{"label":"createStringDict","type":"method","kindLabel":"fun","preview":"createStringDict()","p5DocPath":"createStringDict"},{"label":"createNumberDict","type":"method","kindLabel":"fun","preview":"createNumberDict()","p5DocPath":"createNumberDict"},{"label":"select","type":"method","kindLabel":"fun","params":[{"p":"selectors","o":false},{"p":"container","o":true}],"preview":"select(selectors, [container])","p5DocPath":"select"},{"label":"selectAll","type":"method","kindLabel":"fun","params":[{"p":"selectors","o":false},{"p":"container","o":true}],"preview":"selectAll(selectors, [container])","p5DocPath":"selectAll"},{"label":"removeElements","type":"method","kindLabel":"fun","preview":"removeElements()","p5DocPath":"removeElements"},{"label":"changed","type":"method","kindLabel":"fun","params":[{"p":"fxn","o":false}],"preview":"changed(fxn)","p5DocPath":"changed"},{"label":"input","type":"method","kindLabel":"fun","params":[{"p":"fxn","o":false}],"preview":"input(fxn)","p5DocPath":"input"},{"label":"createDiv","type":"method","kindLabel":"fun","params":[{"p":"html","o":true}],"preview":"createDiv([html])","p5DocPath":"createDiv"},{"label":"createP","type":"method","kindLabel":"fun","params":[{"p":"html","o":true}],"preview":"createP([html])","p5DocPath":"createP"},{"label":"createSpan","type":"method","kindLabel":"fun","params":[{"p":"html","o":true}],"preview":"createSpan([html])","p5DocPath":"createSpan"},{"label":"createImg","type":"method","kindLabel":"fun","preview":"createImg()","p5DocPath":"createImg"},{"label":"createA","type":"method","kindLabel":"fun","params":[{"p":"href","o":false},{"p":"html","o":false},{"p":"target","o":true}],"preview":"createA(href, html, [target])","p5DocPath":"createA"},{"label":"createSlider","type":"method","kindLabel":"fun","params":[{"p":"min","o":false},{"p":"max","o":false},{"p":"value","o":true},{"p":"step","o":true}],"preview":"createSlider(min, max, [value], [step])","p5DocPath":"createSlider"},{"label":"createButton","type":"method","kindLabel":"fun","params":[{"p":"label","o":false},{"p":"value","o":true}],"preview":"createButton(label, [value])","p5DocPath":"createButton"},{"label":"createCheckbox","type":"method","kindLabel":"fun","params":[{"p":"label","o":true},{"p":"value","o":true}],"preview":"createCheckbox([label], [value])","p5DocPath":"createCheckbox"},{"label":"createSelect","type":"method","kindLabel":"fun","preview":"createSelect()","p5DocPath":"createSelect"},{"label":"createRadio","type":"method","kindLabel":"fun","preview":"createRadio()","p5DocPath":"createRadio"},{"label":"createColorPicker","type":"method","kindLabel":"fun","params":[{"p":"value","o":true}],"preview":"createColorPicker([value])","p5DocPath":"createColorPicker"},{"label":"createInput","type":"method","kindLabel":"fun","preview":"createInput()","p5DocPath":"createInput"},{"label":"createFileInput","type":"method","kindLabel":"fun","params":[{"p":"callback","o":false},{"p":"multiple","o":true}],"preview":"createFileInput(callback, [multiple])","p5DocPath":"createFileInput"},{"label":"createVideo","type":"method","kindLabel":"fun","params":[{"p":"src","o":false},{"p":"callback","o":true}],"preview":"createVideo(src, [callback])","p5DocPath":"createVideo"},{"label":"createAudio","type":"method","kindLabel":"fun","params":[{"p":"src","o":true},{"p":"callback","o":true}],"preview":"createAudio([src], [callback])","p5DocPath":"createAudio"},{"label":"createCapture","type":"method","kindLabel":"fun","params":[{"p":"type","o":true},{"p":"flipped","o":true},{"p":"callback","o":true}],"preview":"createCapture([type], [flipped], [callback])","p5DocPath":"createCapture"},{"label":"createElement","type":"method","kindLabel":"fun","params":[{"p":"tag","o":false},{"p":"content","o":true}],"preview":"createElement(tag, [content])","p5DocPath":"createElement"},{"label":"deviceOrientation","type":"variable","kindLabel":"var","params":[],"preview":"deviceOrientation","p5DocPath":"deviceOrientation"},{"label":"accelerationX","type":"variable","kindLabel":"var","params":[],"preview":"accelerationX","p5DocPath":"accelerationX"},{"label":"accelerationY","type":"variable","kindLabel":"var","params":[],"preview":"accelerationY","p5DocPath":"accelerationY"},{"label":"accelerationZ","type":"variable","kindLabel":"var","params":[],"preview":"accelerationZ","p5DocPath":"accelerationZ"},{"label":"pAccelerationX","type":"variable","kindLabel":"var","params":[],"preview":"pAccelerationX","p5DocPath":"pAccelerationX"},{"label":"pAccelerationY","type":"variable","kindLabel":"var","params":[],"preview":"pAccelerationY","p5DocPath":"pAccelerationY"},{"label":"pAccelerationZ","type":"variable","kindLabel":"var","params":[],"preview":"pAccelerationZ","p5DocPath":"pAccelerationZ"},{"label":"rotationX","type":"variable","kindLabel":"var","params":[],"preview":"rotationX","p5DocPath":"rotationX"},{"label":"rotationY","type":"variable","kindLabel":"var","params":[],"preview":"rotationY","p5DocPath":"rotationY"},{"label":"rotationZ","type":"variable","kindLabel":"var","params":[],"preview":"rotationZ","p5DocPath":"rotationZ"},{"label":"pRotationX","type":"variable","kindLabel":"var","params":[],"preview":"pRotationX","p5DocPath":"pRotationX"},{"label":"pRotationY","type":"variable","kindLabel":"var","params":[],"preview":"pRotationY","p5DocPath":"pRotationY"},{"label":"pRotationZ","type":"variable","kindLabel":"var","params":[],"preview":"pRotationZ","p5DocPath":"pRotationZ"},{"label":"turnAxis","type":"variable","kindLabel":"var","params":[],"preview":"turnAxis","p5DocPath":"turnAxis"},{"label":"setMoveThreshold","type":"method","kindLabel":"fun","params":[{"p":"value","o":false}],"preview":"setMoveThreshold(value)","p5DocPath":"setMoveThreshold"},{"label":"setShakeThreshold","type":"method","kindLabel":"fun","params":[{"p":"value","o":false}],"preview":"setShakeThreshold(value)","p5DocPath":"setShakeThreshold"},{"label":"deviceMoved","type":"method","kindLabel":"fun","preview":"deviceMoved()","p5DocPath":"deviceMoved"},{"label":"deviceTurned","type":"method","kindLabel":"fun","preview":"deviceTurned()","p5DocPath":"deviceTurned"},{"label":"deviceShaken","type":"method","kindLabel":"fun","preview":"deviceShaken()","p5DocPath":"deviceShaken"},{"label":"keyIsPressed","type":"variable","kindLabel":"var","params":[],"preview":"keyIsPressed","p5DocPath":"keyIsPressed"},{"label":"key","type":"variable","kindLabel":"var","params":[],"preview":"key","p5DocPath":"key"},{"label":"keyCode","type":"variable","kindLabel":"var","params":[],"preview":"keyCode","p5DocPath":"keyCode"},{"label":"keyPressed","type":"method","kindLabel":"fun","params":[{"p":"event","o":true}],"preview":"keyPressed([event])","p5DocPath":"keyPressed"},{"label":"keyReleased","type":"method","kindLabel":"fun","params":[{"p":"event","o":true}],"preview":"keyReleased([event])","p5DocPath":"keyReleased"},{"label":"keyTyped","type":"method","kindLabel":"fun","params":[{"p":"event","o":true}],"preview":"keyTyped([event])","p5DocPath":"keyTyped"},{"label":"keyIsDown","type":"method","kindLabel":"fun","params":[{"p":"code","o":false}],"preview":"keyIsDown(code)","p5DocPath":"keyIsDown"},{"label":"movedX","type":"variable","kindLabel":"var","params":[],"preview":"movedX","p5DocPath":"movedX"},{"label":"movedY","type":"variable","kindLabel":"var","params":[],"preview":"movedY","p5DocPath":"movedY"},{"label":"mouseX","type":"variable","kindLabel":"var","params":[],"preview":"mouseX","p5DocPath":"mouseX"},{"label":"mouseY","type":"variable","kindLabel":"var","params":[],"preview":"mouseY","p5DocPath":"mouseY"},{"label":"pmouseX","type":"variable","kindLabel":"var","params":[],"preview":"pmouseX","p5DocPath":"pmouseX"},{"label":"pmouseY","type":"variable","kindLabel":"var","params":[],"preview":"pmouseY","p5DocPath":"pmouseY"},{"label":"winMouseX","type":"variable","kindLabel":"var","params":[],"preview":"winMouseX","p5DocPath":"winMouseX"},{"label":"winMouseY","type":"variable","kindLabel":"var","params":[],"preview":"winMouseY","p5DocPath":"winMouseY"},{"label":"pwinMouseX","type":"variable","kindLabel":"var","params":[],"preview":"pwinMouseX","p5DocPath":"pwinMouseX"},{"label":"pwinMouseY","type":"variable","kindLabel":"var","params":[],"preview":"pwinMouseY","p5DocPath":"pwinMouseY"},{"label":"mouseButton","type":"variable","kindLabel":"var","params":[],"preview":"mouseButton","p5DocPath":"mouseButton"},{"label":"mouseIsPressed","type":"variable","kindLabel":"var","params":[],"preview":"mouseIsPressed","p5DocPath":"mouseIsPressed"},{"label":"mouseMoved","type":"method","kindLabel":"fun","params":[{"p":"event","o":true}],"preview":"mouseMoved([event])","p5DocPath":"mouseMoved"},{"label":"mouseDragged","type":"method","kindLabel":"fun","params":[{"p":"event","o":true}],"preview":"mouseDragged([event])","p5DocPath":"mouseDragged"},{"label":"mousePressed","type":"method","kindLabel":"fun","params":[{"p":"event","o":true}],"preview":"mousePressed([event])","p5DocPath":"mousePressed"},{"label":"mouseReleased","type":"method","kindLabel":"fun","params":[{"p":"event","o":true}],"preview":"mouseReleased([event])","p5DocPath":"mouseReleased"},{"label":"mouseClicked","type":"method","kindLabel":"fun","params":[{"p":"event","o":true}],"preview":"mouseClicked([event])","p5DocPath":"mouseClicked"},{"label":"doubleClicked","type":"method","kindLabel":"fun","params":[{"p":"event","o":true}],"preview":"doubleClicked([event])","p5DocPath":"doubleClicked"},{"label":"mouseWheel","type":"method","kindLabel":"fun","params":[{"p":"event","o":true}],"preview":"mouseWheel([event])","p5DocPath":"mouseWheel"},{"label":"requestPointerLock","type":"method","kindLabel":"fun","preview":"requestPointerLock()","p5DocPath":"requestPointerLock"},{"label":"exitPointerLock","type":"method","kindLabel":"fun","preview":"exitPointerLock()","p5DocPath":"exitPointerLock"},{"label":"touches","type":"variable","kindLabel":"var","params":[],"preview":"touches","p5DocPath":"touches"},{"label":"touchStarted","type":"method","kindLabel":"fun","params":[{"p":"event","o":true}],"preview":"touchStarted([event])","p5DocPath":"touchStarted"},{"label":"touchMoved","type":"method","kindLabel":"fun","params":[{"p":"event","o":true}],"preview":"touchMoved([event])","p5DocPath":"touchMoved"},{"label":"touchEnded","type":"method","kindLabel":"fun","params":[{"p":"event","o":true}],"preview":"touchEnded([event])","p5DocPath":"touchEnded"},{"label":"createImage","type":"method","kindLabel":"fun","params":[{"p":"width","o":false},{"p":"height","o":false}],"preview":"createImage(width, height)","p5DocPath":"createImage"},{"label":"saveCanvas","type":"method","kindLabel":"fun","preview":"saveCanvas()","p5DocPath":"saveCanvas"},{"label":"saveFrames","type":"method","kindLabel":"fun","params":[{"p":"filename","o":false},{"p":"extension","o":false},{"p":"duration","o":false},{"p":"framerate","o":false},{"p":"callback","o":true}],"preview":"saveFrames(filename, extension, duration, framerate, [callback])","p5DocPath":"saveFrames"},{"label":"loadImage","type":"method","kindLabel":"fun","params":[{"p":"path","o":false},{"p":"successCallback","o":true},{"p":"failureCallback","o":true}],"preview":"loadImage(path, [successCallback], [failureCallback])","p5DocPath":"loadImage"},{"label":"saveGif","type":"method","kindLabel":"fun","params":[{"p":"filename","o":false},{"p":"duration","o":false},{"p":"options","o":true}],"preview":"saveGif(filename, duration, [options])","p5DocPath":"saveGif"},{"label":"image","type":"method","kindLabel":"fun","preview":"image()","p5DocPath":"image"},{"label":"tint","type":"method","kindLabel":"fun","preview":"tint()","p5DocPath":"tint"},{"label":"noTint","type":"method","kindLabel":"fun","preview":"noTint()","p5DocPath":"noTint"},{"label":"imageMode","type":"method","kindLabel":"fun","params":[{"p":"mode","o":false}],"preview":"imageMode(mode)","p5DocPath":"imageMode"},{"label":"pixels","type":"variable","kindLabel":"var","params":[],"preview":"pixels","p5DocPath":"pixels"},{"label":"blend","type":"method","kindLabel":"fun","preview":"blend()","p5DocPath":"blend"},{"label":"copy","type":"method","kindLabel":"fun","preview":"copy()","p5DocPath":"copy"},{"label":"filter","type":"method","kindLabel":"fun","preview":"filter()","p5DocPath":"filter"},{"label":"get","type":"method","kindLabel":"fun","preview":"get()","p5DocPath":"get"},{"label":"loadPixels","type":"method","kindLabel":"fun","preview":"loadPixels()","p5DocPath":"loadPixels"},{"label":"set","type":"method","kindLabel":"fun","params":[{"p":"x","o":false},{"p":"y","o":false},{"p":"c","o":false}],"preview":"set(x, y, c)","p5DocPath":"set"},{"label":"updatePixels","type":"method","kindLabel":"fun","params":[{"p":"x","o":true},{"p":"y","o":true},{"p":"w","o":true},{"p":"h","o":true}],"preview":"updatePixels([x], [y], [w], [h])","p5DocPath":"updatePixels"},{"label":"loadJSON","type":"method","kindLabel":"fun","params":[{"p":"path","o":false},{"p":"successCallback","o":true},{"p":"errorCallback","o":true}],"preview":"loadJSON(path, [successCallback], [errorCallback])","p5DocPath":"loadJSON"},{"label":"loadStrings","type":"method","kindLabel":"fun","params":[{"p":"path","o":false},{"p":"successCallback","o":true},{"p":"errorCallback","o":true}],"preview":"loadStrings(path, [successCallback], [errorCallback])","p5DocPath":"loadStrings"},{"label":"loadTable","type":"method","kindLabel":"fun","params":[{"p":"filename","o":false},{"p":"extension","o":true},{"p":"header","o":true},{"p":"callback","o":true},{"p":"errorCallback","o":true}],"preview":"loadTable(filename, [extension], [header], [callback], [errorCallback])","p5DocPath":"loadTable"},{"label":"loadXML","type":"method","kindLabel":"fun","params":[{"p":"path","o":false},{"p":"successCallback","o":true},{"p":"errorCallback","o":true}],"preview":"loadXML(path, [successCallback], [errorCallback])","p5DocPath":"loadXML"},{"label":"loadBytes","type":"method","kindLabel":"fun","params":[{"p":"file","o":false},{"p":"callback","o":true},{"p":"errorCallback","o":true}],"preview":"loadBytes(file, [callback], [errorCallback])","p5DocPath":"loadBytes"},{"label":"httpGet","type":"method","kindLabel":"fun","preview":"httpGet()","p5DocPath":"httpGet"},{"label":"httpPost","type":"method","kindLabel":"fun","preview":"httpPost()","p5DocPath":"httpPost"},{"label":"httpDo","type":"method","kindLabel":"fun","preview":"httpDo()","p5DocPath":"httpDo"},{"label":"createWriter","type":"method","kindLabel":"fun","params":[{"p":"name","o":false},{"p":"extension","o":true}],"preview":"createWriter(name, [extension])","p5DocPath":"createWriter"},{"label":"save","type":"method","kindLabel":"fun","params":[{"p":"objectOrFilename","o":true},{"p":"filename","o":true},{"p":"options","o":true}],"preview":"save([objectOrFilename], [filename], [options])","p5DocPath":"save"},{"label":"saveJSON","type":"method","kindLabel":"fun","params":[{"p":"json","o":false},{"p":"filename","o":false},{"p":"optimize","o":true}],"preview":"saveJSON(json, filename, [optimize])","p5DocPath":"saveJSON"},{"label":"saveStrings","type":"method","kindLabel":"fun","params":[{"p":"list","o":false},{"p":"filename","o":false},{"p":"extension","o":true},{"p":"isCRLF","o":true}],"preview":"saveStrings(list, filename, [extension], [isCRLF])","p5DocPath":"saveStrings"},{"label":"saveTable","type":"method","kindLabel":"fun","params":[{"p":"Table","o":false},{"p":"filename","o":false},{"p":"options","o":true}],"preview":"saveTable(Table, filename, [options])","p5DocPath":"saveTable"},{"label":"abs","type":"method","kindLabel":"fun","params":[{"p":"n","o":false}],"preview":"abs(n)","p5DocPath":"abs"},{"label":"ceil","type":"method","kindLabel":"fun","params":[{"p":"n","o":false}],"preview":"ceil(n)","p5DocPath":"ceil"},{"label":"constrain","type":"method","kindLabel":"fun","params":[{"p":"n","o":false},{"p":"low","o":false},{"p":"high","o":false}],"preview":"constrain(n, low, high)","p5DocPath":"constrain"},{"label":"dist","type":"method","kindLabel":"fun","preview":"dist()","p5DocPath":"dist"},{"label":"exp","type":"method","kindLabel":"fun","params":[{"p":"n","o":false}],"preview":"exp(n)","p5DocPath":"exp"},{"label":"floor","type":"method","kindLabel":"fun","params":[{"p":"n","o":false}],"preview":"floor(n)","p5DocPath":"floor"},{"label":"lerp","type":"method","kindLabel":"fun","params":[{"p":"start","o":false},{"p":"stop","o":false},{"p":"amt","o":false}],"preview":"lerp(start, stop, amt)","p5DocPath":"lerp"},{"label":"log","type":"method","kindLabel":"fun","params":[{"p":"n","o":false}],"preview":"log(n)","p5DocPath":"log"},{"label":"mag","type":"method","kindLabel":"fun","params":[{"p":"x","o":false},{"p":"y","o":false}],"preview":"mag(x, y)","p5DocPath":"mag"},{"label":"map","type":"method","kindLabel":"fun","params":[{"p":"value","o":false},{"p":"start1","o":false},{"p":"stop1","o":false},{"p":"start2","o":false},{"p":"stop2","o":false},{"p":"withinBounds","o":true}],"preview":"map(value, start1, stop1, start2, stop2, [withinBounds])","p5DocPath":"map"},{"label":"max","type":"method","kindLabel":"fun","preview":"max()","p5DocPath":"max"},{"label":"min","type":"method","kindLabel":"fun","preview":"min()","p5DocPath":"min"},{"label":"norm","type":"method","kindLabel":"fun","params":[{"p":"value","o":false},{"p":"start","o":false},{"p":"stop","o":false}],"preview":"norm(value, start, stop)","p5DocPath":"norm"},{"label":"pow","type":"method","kindLabel":"fun","params":[{"p":"n","o":false},{"p":"e","o":false}],"preview":"pow(n, e)","p5DocPath":"pow"},{"label":"round","type":"method","kindLabel":"fun","params":[{"p":"n","o":false},{"p":"decimals","o":true}],"preview":"round(n, [decimals])","p5DocPath":"round"},{"label":"sq","type":"method","kindLabel":"fun","params":[{"p":"n","o":false}],"preview":"sq(n)","p5DocPath":"sq"},{"label":"sqrt","type":"method","kindLabel":"fun","params":[{"p":"n","o":false}],"preview":"sqrt(n)","p5DocPath":"sqrt"},{"label":"fract","type":"method","kindLabel":"fun","params":[{"p":"n","o":false}],"preview":"fract(n)","p5DocPath":"fract"},{"label":"createVector","type":"method","kindLabel":"fun","params":[{"p":"x","o":true},{"p":"y","o":true},{"p":"z","o":true}],"preview":"createVector([x], [y], [z])","p5DocPath":"createVector"},{"label":"noise","type":"method","kindLabel":"fun","params":[{"p":"x","o":false},{"p":"y","o":true},{"p":"z","o":true}],"preview":"noise(x, [y], [z])","p5DocPath":"noise"},{"label":"noiseDetail","type":"method","kindLabel":"fun","params":[{"p":"lod","o":false},{"p":"falloff","o":false}],"preview":"noiseDetail(lod, falloff)","p5DocPath":"noiseDetail"},{"label":"noiseSeed","type":"method","kindLabel":"fun","params":[{"p":"seed","o":false}],"preview":"noiseSeed(seed)","p5DocPath":"noiseSeed"},{"label":"randomSeed","type":"method","kindLabel":"fun","params":[{"p":"seed","o":false}],"preview":"randomSeed(seed)","p5DocPath":"randomSeed"},{"label":"random","type":"method","kindLabel":"fun","preview":"random()","p5DocPath":"random"},{"label":"randomGaussian","type":"method","kindLabel":"fun","params":[{"p":"mean","o":true},{"p":"sd","o":true}],"preview":"randomGaussian([mean], [sd])","p5DocPath":"randomGaussian"},{"label":"acos","type":"method","kindLabel":"fun","params":[{"p":"value","o":false}],"preview":"acos(value)","p5DocPath":"acos"},{"label":"asin","type":"method","kindLabel":"fun","params":[{"p":"value","o":false}],"preview":"asin(value)","p5DocPath":"asin"},{"label":"atan","type":"method","kindLabel":"fun","params":[{"p":"value","o":false}],"preview":"atan(value)","p5DocPath":"atan"},{"label":"atan2","type":"method","kindLabel":"fun","params":[{"p":"y","o":false},{"p":"x","o":false}],"preview":"atan2(y, x)","p5DocPath":"atan2"},{"label":"cos","type":"method","kindLabel":"fun","params":[{"p":"angle","o":false}],"preview":"cos(angle)","p5DocPath":"cos"},{"label":"sin","type":"method","kindLabel":"fun","params":[{"p":"angle","o":false}],"preview":"sin(angle)","p5DocPath":"sin"},{"label":"tan","type":"method","kindLabel":"fun","params":[{"p":"angle","o":false}],"preview":"tan(angle)","p5DocPath":"tan"},{"label":"degrees","type":"method","kindLabel":"fun","params":[{"p":"radians","o":false}],"preview":"degrees(radians)","p5DocPath":"degrees"},{"label":"radians","type":"method","kindLabel":"fun","params":[{"p":"degrees","o":false}],"preview":"radians(degrees)","p5DocPath":"radians"},{"label":"angleMode","type":"method","kindLabel":"fun","preview":"angleMode()","p5DocPath":"angleMode"},{"label":"textAlign","type":"method","kindLabel":"fun","preview":"textAlign()","p5DocPath":"textAlign"},{"label":"textLeading","type":"method","kindLabel":"fun","preview":"textLeading()","p5DocPath":"textLeading"},{"label":"textSize","type":"method","kindLabel":"fun","preview":"textSize()","p5DocPath":"textSize"},{"label":"textStyle","type":"method","kindLabel":"fun","preview":"textStyle()","p5DocPath":"textStyle"},{"label":"textWidth","type":"method","kindLabel":"fun","params":[{"p":"str","o":false}],"preview":"textWidth(str)","p5DocPath":"textWidth"},{"label":"textAscent","type":"method","kindLabel":"fun","preview":"textAscent()","p5DocPath":"textAscent"},{"label":"textDescent","type":"method","kindLabel":"fun","preview":"textDescent()","p5DocPath":"textDescent"},{"label":"textWrap","type":"method","kindLabel":"fun","params":[{"p":"style","o":false}],"preview":"textWrap(style)","p5DocPath":"textWrap"},{"label":"loadFont","type":"method","kindLabel":"fun","params":[{"p":"path","o":false},{"p":"successCallback","o":true},{"p":"failureCallback","o":true}],"preview":"loadFont(path, [successCallback], [failureCallback])","p5DocPath":"loadFont"},{"label":"text","type":"method","kindLabel":"fun","params":[{"p":"str","o":false},{"p":"x","o":false},{"p":"y","o":false},{"p":"maxWidth","o":true},{"p":"maxHeight","o":true}],"preview":"text(str, x, y, [maxWidth], [maxHeight])","p5DocPath":"text"},{"label":"textFont","type":"method","kindLabel":"fun","preview":"textFont()","p5DocPath":"textFont"},{"label":"append","type":"method","kindLabel":"fun","params":[{"p":"array","o":false},{"p":"value","o":false}],"preview":"append(array, value)","p5DocPath":"append"},{"label":"arrayCopy","type":"method","kindLabel":"fun","preview":"arrayCopy()","p5DocPath":"arrayCopy"},{"label":"concat","type":"method","kindLabel":"fun","params":[{"p":"a","o":false},{"p":"b","o":false}],"preview":"concat(a, b)","p5DocPath":"concat"},{"label":"reverse","type":"method","kindLabel":"fun","params":[{"p":"list","o":false}],"preview":"reverse(list)","p5DocPath":"reverse"},{"label":"shorten","type":"method","kindLabel":"fun","params":[{"p":"list","o":false}],"preview":"shorten(list)","p5DocPath":"shorten"},{"label":"shuffle","type":"method","kindLabel":"fun","params":[{"p":"array","o":false},{"p":"bool","o":true}],"preview":"shuffle(array, [bool])","p5DocPath":"shuffle"},{"label":"sort","type":"method","kindLabel":"fun","params":[{"p":"list","o":false},{"p":"count","o":true}],"preview":"sort(list, [count])","p5DocPath":"sort"},{"label":"splice","type":"method","kindLabel":"fun","params":[{"p":"list","o":false},{"p":"value","o":false},{"p":"position","o":false}],"preview":"splice(list, value, position)","p5DocPath":"splice"},{"label":"subset","type":"method","kindLabel":"fun","params":[{"p":"list","o":false},{"p":"start","o":false},{"p":"count","o":true}],"preview":"subset(list, start, [count])","p5DocPath":"subset"},{"label":"float","type":"method","kindLabel":"fun","preview":"float()","p5DocPath":"float"},{"label":"int","type":"method","kindLabel":"fun","preview":"int()","p5DocPath":"int"},{"label":"str","type":"method","kindLabel":"fun","params":[{"p":"n","o":false}],"preview":"str(n)","p5DocPath":"str"},{"label":"boolean","type":"method","kindLabel":"fun","preview":"boolean()","p5DocPath":"boolean"},{"label":"byte","type":"method","kindLabel":"fun","preview":"byte()","p5DocPath":"byte"},{"label":"char","type":"method","kindLabel":"fun","preview":"char()","p5DocPath":"char"},{"label":"unchar","type":"method","kindLabel":"fun","preview":"unchar()","p5DocPath":"unchar"},{"label":"hex","type":"method","kindLabel":"fun","preview":"hex()","p5DocPath":"hex"},{"label":"unhex","type":"method","kindLabel":"fun","preview":"unhex()","p5DocPath":"unhex"},{"label":"join","type":"method","kindLabel":"fun","params":[{"p":"list","o":false},{"p":"separator","o":false}],"preview":"join(list, separator)","p5DocPath":"join"},{"label":"match","type":"method","kindLabel":"fun","params":[{"p":"str","o":false},{"p":"regexp","o":false}],"preview":"match(str, regexp)","p5DocPath":"match"},{"label":"matchAll","type":"method","kindLabel":"fun","params":[{"p":"str","o":false},{"p":"regexp","o":false}],"preview":"matchAll(str, regexp)","p5DocPath":"matchAll"},{"label":"nf","type":"method","kindLabel":"fun","preview":"nf()","p5DocPath":"nf"},{"label":"nfc","type":"method","kindLabel":"fun","preview":"nfc()","p5DocPath":"nfc"},{"label":"nfp","type":"method","kindLabel":"fun","preview":"nfp()","p5DocPath":"nfp"},{"label":"nfs","type":"method","kindLabel":"fun","preview":"nfs()","p5DocPath":"nfs"},{"label":"split","type":"method","kindLabel":"fun","params":[{"p":"value","o":false},{"p":"delim","o":false}],"preview":"split(value, delim)","p5DocPath":"split"},{"label":"splitTokens","type":"method","kindLabel":"fun","params":[{"p":"value","o":false},{"p":"delim","o":true}],"preview":"splitTokens(value, [delim])","p5DocPath":"splitTokens"},{"label":"trim","type":"method","kindLabel":"fun","preview":"trim()","p5DocPath":"trim"},{"label":"day","type":"method","kindLabel":"fun","preview":"day()","p5DocPath":"day"},{"label":"hour","type":"method","kindLabel":"fun","preview":"hour()","p5DocPath":"hour"},{"label":"minute","type":"method","kindLabel":"fun","preview":"minute()","p5DocPath":"minute"},{"label":"millis","type":"method","kindLabel":"fun","preview":"millis()","p5DocPath":"millis"},{"label":"month","type":"method","kindLabel":"fun","preview":"month()","p5DocPath":"month"},{"label":"second","type":"method","kindLabel":"fun","preview":"second()","p5DocPath":"second"},{"label":"year","type":"method","kindLabel":"fun","preview":"year()","p5DocPath":"year"},{"label":"beginGeometry","type":"method","kindLabel":"fun","preview":"beginGeometry()","p5DocPath":"beginGeometry"},{"label":"endGeometry","type":"method","kindLabel":"fun","preview":"endGeometry()","p5DocPath":"endGeometry"},{"label":"buildGeometry","type":"method","kindLabel":"fun","params":[{"p":"callback","o":false}],"preview":"buildGeometry(callback)","p5DocPath":"buildGeometry"},{"label":"freeGeometry","type":"method","kindLabel":"fun","params":[{"p":"geometry","o":false}],"preview":"freeGeometry(geometry)","p5DocPath":"freeGeometry"},{"label":"plane","type":"method","kindLabel":"fun","params":[{"p":"width","o":true},{"p":"height","o":true},{"p":"detailX","o":true},{"p":"detailY","o":true}],"preview":"plane([width], [height], [detailX], [detailY])","p5DocPath":"plane"},{"label":"box","type":"method","kindLabel":"fun","params":[{"p":"width","o":true},{"p":"height","o":true},{"p":"depth","o":true},{"p":"detailX","o":true},{"p":"detailY","o":true}],"preview":"box([width], [height], [depth], [detailX], [detailY])","p5DocPath":"box"},{"label":"sphere","type":"method","kindLabel":"fun","params":[{"p":"radius","o":true},{"p":"detailX","o":true},{"p":"detailY","o":true}],"preview":"sphere([radius], [detailX], [detailY])","p5DocPath":"sphere"},{"label":"cylinder","type":"method","kindLabel":"fun","params":[{"p":"radius","o":true},{"p":"height","o":true},{"p":"detailX","o":true},{"p":"detailY","o":true},{"p":"bottomCap","o":true},{"p":"topCap","o":true}],"preview":"cylinder([radius], [height], [detailX], [detailY], [bottomCap], [topCap])","p5DocPath":"cylinder"},{"label":"cone","type":"method","kindLabel":"fun","params":[{"p":"radius","o":true},{"p":"height","o":true},{"p":"detailX","o":true},{"p":"detailY","o":true},{"p":"cap","o":true}],"preview":"cone([radius], [height], [detailX], [detailY], [cap])","p5DocPath":"cone"},{"label":"ellipsoid","type":"method","kindLabel":"fun","params":[{"p":"radiusX","o":true},{"p":"radiusY","o":true},{"p":"radiusZ","o":true},{"p":"detailX","o":true},{"p":"detailY","o":true}],"preview":"ellipsoid([radiusX], [radiusY], [radiusZ], [detailX], [detailY])","p5DocPath":"ellipsoid"},{"label":"torus","type":"method","kindLabel":"fun","params":[{"p":"radius","o":true},{"p":"tubeRadius","o":true},{"p":"detailX","o":true},{"p":"detailY","o":true}],"preview":"torus([radius], [tubeRadius], [detailX], [detailY])","p5DocPath":"torus"},{"label":"orbitControl","type":"method","kindLabel":"fun","params":[{"p":"sensitivityX","o":true},{"p":"sensitivityY","o":true},{"p":"sensitivityZ","o":true},{"p":"options","o":true}],"preview":"orbitControl([sensitivityX], [sensitivityY], [sensitivityZ], [options])","p5DocPath":"orbitControl"},{"label":"debugMode","type":"method","kindLabel":"fun","preview":"debugMode()","p5DocPath":"debugMode"},{"label":"noDebugMode","type":"method","kindLabel":"fun","preview":"noDebugMode()","p5DocPath":"noDebugMode"},{"label":"ambientLight","type":"method","kindLabel":"fun","preview":"ambientLight()","p5DocPath":"ambientLight"},{"label":"specularColor","type":"method","kindLabel":"fun","preview":"specularColor()","p5DocPath":"specularColor"},{"label":"directionalLight","type":"method","kindLabel":"fun","preview":"directionalLight()","p5DocPath":"directionalLight"},{"label":"pointLight","type":"method","kindLabel":"fun","preview":"pointLight()","p5DocPath":"pointLight"},{"label":"imageLight","type":"method","kindLabel":"fun","params":[{"p":"img","o":false}],"preview":"imageLight(img)","p5DocPath":"imageLight"},{"label":"panorama","type":"method","kindLabel":"fun","params":[{"p":"img","o":false}],"preview":"panorama(img)","p5DocPath":"panorama"},{"label":"lights","type":"method","kindLabel":"fun","preview":"lights()","p5DocPath":"lights"},{"label":"lightFalloff","type":"method","kindLabel":"fun","params":[{"p":"constant","o":false},{"p":"linear","o":false},{"p":"quadratic","o":false}],"preview":"lightFalloff(constant, linear, quadratic)","p5DocPath":"lightFalloff"},{"label":"spotLight","type":"method","kindLabel":"fun","preview":"spotLight()","p5DocPath":"spotLight"},{"label":"noLights","type":"method","kindLabel":"fun","preview":"noLights()","p5DocPath":"noLights"},{"label":"loadModel","type":"method","kindLabel":"fun","preview":"loadModel()","p5DocPath":"loadModel"},{"label":"model","type":"method","kindLabel":"fun","params":[{"p":"model","o":false}],"preview":"model(model)","p5DocPath":"model"},{"label":"createModel","type":"method","kindLabel":"fun","preview":"createModel()","p5DocPath":"createModel"},{"label":"loadShader","type":"method","kindLabel":"fun","params":[{"p":"vertFilename","o":false},{"p":"fragFilename","o":false},{"p":"successCallback","o":true},{"p":"failureCallback","o":true}],"preview":"loadShader(vertFilename, fragFilename, [successCallback], [failureCallback])","p5DocPath":"loadShader"},{"label":"createShader","type":"method","kindLabel":"fun","params":[{"p":"vertSrc","o":false},{"p":"fragSrc","o":false},{"p":"options","o":true}],"preview":"createShader(vertSrc, fragSrc, [options])","p5DocPath":"createShader"},{"label":"createFilterShader","type":"method","kindLabel":"fun","params":[{"p":"fragSrc","o":false}],"preview":"createFilterShader(fragSrc)","p5DocPath":"createFilterShader"},{"label":"shader","type":"method","kindLabel":"fun","params":[{"p":"s","o":false}],"preview":"shader(s)","p5DocPath":"shader"},{"label":"baseMaterialShader","type":"method","kindLabel":"fun","preview":"baseMaterialShader()","p5DocPath":"baseMaterialShader"},{"label":"baseNormalShader","type":"method","kindLabel":"fun","preview":"baseNormalShader()","p5DocPath":"baseNormalShader"},{"label":"baseColorShader","type":"method","kindLabel":"fun","preview":"baseColorShader()","p5DocPath":"baseColorShader"},{"label":"baseStrokeShader","type":"method","kindLabel":"fun","preview":"baseStrokeShader()","p5DocPath":"baseStrokeShader"},{"label":"resetShader","type":"method","kindLabel":"fun","preview":"resetShader()","p5DocPath":"resetShader"},{"label":"texture","type":"method","kindLabel":"fun","params":[{"p":"tex","o":false}],"preview":"texture(tex)","p5DocPath":"texture"},{"label":"textureMode","type":"method","kindLabel":"fun","params":[{"p":"mode","o":false}],"preview":"textureMode(mode)","p5DocPath":"textureMode"},{"label":"textureWrap","type":"method","kindLabel":"fun","params":[{"p":"wrapX","o":false},{"p":"wrapY","o":true}],"preview":"textureWrap(wrapX, [wrapY])","p5DocPath":"textureWrap"},{"label":"normalMaterial","type":"method","kindLabel":"fun","preview":"normalMaterial()","p5DocPath":"normalMaterial"},{"label":"ambientMaterial","type":"method","kindLabel":"fun","preview":"ambientMaterial()","p5DocPath":"ambientMaterial"},{"label":"emissiveMaterial","type":"method","kindLabel":"fun","preview":"emissiveMaterial()","p5DocPath":"emissiveMaterial"},{"label":"specularMaterial","type":"method","kindLabel":"fun","preview":"specularMaterial()","p5DocPath":"specularMaterial"},{"label":"shininess","type":"method","kindLabel":"fun","params":[{"p":"shine","o":false}],"preview":"shininess(shine)","p5DocPath":"shininess"},{"label":"metalness","type":"method","kindLabel":"fun","params":[{"p":"metallic","o":false}],"preview":"metalness(metallic)","p5DocPath":"metalness"},{"label":"camera","type":"method","kindLabel":"fun","params":[{"p":"x","o":true},{"p":"y","o":true},{"p":"z","o":true},{"p":"centerX","o":true},{"p":"centerY","o":true},{"p":"centerZ","o":true},{"p":"upX","o":true},{"p":"upY","o":true},{"p":"upZ","o":true}],"preview":"camera([x], [y], [z], [centerX], [centerY], [centerZ], [upX], [upY], [upZ])","p5DocPath":"camera"},{"label":"perspective","type":"method","kindLabel":"fun","params":[{"p":"fovy","o":true},{"p":"aspect","o":true},{"p":"near","o":true},{"p":"far","o":true}],"preview":"perspective([fovy], [aspect], [near], [far])","p5DocPath":"perspective"},{"label":"linePerspective","type":"method","kindLabel":"fun","preview":"linePerspective()","p5DocPath":"linePerspective"},{"label":"ortho","type":"method","kindLabel":"fun","params":[{"p":"left","o":true},{"p":"right","o":true},{"p":"bottom","o":true},{"p":"top","o":true},{"p":"near","o":true},{"p":"far","o":true}],"preview":"ortho([left], [right], [bottom], [top], [near], [far])","p5DocPath":"ortho"},{"label":"frustum","type":"method","kindLabel":"fun","params":[{"p":"left","o":true},{"p":"right","o":true},{"p":"bottom","o":true},{"p":"top","o":true},{"p":"near","o":true},{"p":"far","o":true}],"preview":"frustum([left], [right], [bottom], [top], [near], [far])","p5DocPath":"frustum"},{"label":"createCamera","type":"method","kindLabel":"fun","preview":"createCamera()","p5DocPath":"createCamera"},{"label":"setCamera","type":"method","kindLabel":"fun","params":[{"p":"cam","o":false}],"preview":"setCamera(cam)","p5DocPath":"setCamera"},{"label":"setAttributes","type":"method","kindLabel":"fun","preview":"setAttributes()","p5DocPath":"setAttributes"},{"label":"getAudioContext","type":"method","kindLabel":"fun","preview":"getAudioContext()","p5DocPath":"getAudioContext"},{"label":"userStartAudio","type":"method","kindLabel":"fun","params":[{"p":"elements","o":true},{"p":"callback","o":true}],"preview":"userStartAudio([elements], [callback])","p5DocPath":"userStartAudio"},{"label":"getOutputVolume","type":"method","kindLabel":"fun","preview":"getOutputVolume()","p5DocPath":"getOutputVolume"},{"label":"outputVolume","type":"method","kindLabel":"fun","params":[{"p":"volume","o":false},{"p":"rampTime","o":true},{"p":"timeFromNow","o":true}],"preview":"outputVolume(volume, [rampTime], [timeFromNow])","p5DocPath":"outputVolume"},{"label":"soundOut","type":"variable","kindLabel":"var","params":[],"preview":"soundOut","p5DocPath":"soundOut"},{"label":"sampleRate","type":"method","kindLabel":"fun","preview":"sampleRate()","p5DocPath":"sampleRate"},{"label":"freqToMidi","type":"method","kindLabel":"fun","params":[{"p":"frequency","o":false}],"preview":"freqToMidi(frequency)","p5DocPath":"freqToMidi"},{"label":"midiToFreq","type":"method","kindLabel":"fun","params":[{"p":"midiNote","o":false}],"preview":"midiToFreq(midiNote)","p5DocPath":"midiToFreq"},{"label":"soundFormats","type":"method","kindLabel":"fun","params":[{"p":"formats","o":true}],"preview":"soundFormats([formats])","p5DocPath":"soundFormats"},{"label":"saveSound","type":"method","kindLabel":"fun","params":[{"p":"soundFile","o":false},{"p":"fileName","o":false}],"preview":"saveSound(soundFile, fileName)","p5DocPath":"saveSound"},{"label":"loadSound","type":"method","kindLabel":"fun","params":[{"p":"path","o":false},{"p":"successCallback","o":true},{"p":"errorCallback","o":true},{"p":"whileLoading","o":true}],"preview":"loadSound(path, [successCallback], [errorCallback], [whileLoading])","p5DocPath":"loadSound"},{"label":"createConvolver","type":"method","kindLabel":"fun","params":[{"p":"path","o":false},{"p":"callback","o":true},{"p":"errorCallback","o":true}],"preview":"createConvolver(path, [callback], [errorCallback])","p5DocPath":"createConvolver"},{"label":"setBPM","type":"method","kindLabel":"fun","params":[{"p":"BPM","o":false},{"p":"rampTime","o":false}],"preview":"setBPM(BPM, rampTime)","p5DocPath":"setBPM"},{"label":"true","type":"boolean","kindLabel":"bool","params":[],"preview":"true","p5DocPath":"boolean"},{"label":"false","type":"boolean","kindLabel":"bool","params":[],"preview":"false","p5DocPath":"boolean"},{"label":"await","type":"keyword","kindLabel":"keyword","params":[],"preview":"await"},{"label":"class","type":"keyword","kindLabel":"keyword","params":[],"preview":"class","p5DocPath":"class"},{"label":"const","type":"keyword","kindLabel":"keyword","params":[],"preview":"const","p5DocPath":"const"},{"label":"else","type":"keyword","kindLabel":"keyword","params":[],"preview":"else","p5DocPath":"if-else"},{"label":"export","type":"keyword","kindLabel":"keyword","params":[],"preview":"export"},{"label":"for","type":"keyword","kindLabel":"keyword","params":[],"preview":"for","p5DocPath":"for"},{"label":"function","type":"keyword","kindLabel":"keyword","params":[],"preview":"function","p5DocPath":"function"},{"label":"if","type":"keyword","kindLabel":"keyword","params":[],"preview":"if","p5DocPath":"if-else"},{"label":"return","type":"keyword","kindLabel":"keyword","params":[],"preview":"return","p5DocPath":"return"},{"label":"while","type":"keyword","kindLabel":"keyword","params":[],"preview":"while","p5DocPath":"while"},{"label":"with","type":"keyword","kindLabel":"keyword","params":[],"preview":"with"},{"label":"let","type":"keyword","kindLabel":"keyword","params":[],"preview":"let","p5DocPath":"let"},{"label":"Array","type":"obj","kindLabel":"obj","params":[],"preview":"Array"},{"label":"Boolean","type":"obj","kindLabel":"obj","params":[],"preview":"Boolean"},{"label":"Date","type":"obj","kindLabel":"obj","params":[],"preview":"Date"},{"label":"Error","type":"obj","kindLabel":"obj","params":[],"preview":"Error"},{"label":"Function","type":"obj","kindLabel":"obj","params":[],"preview":"Function"},{"label":"JSON","type":"obj","kindLabel":"obj","params":[],"preview":"JSON","p5DocPath":"JSON"},{"label":"Math","type":"obj","kindLabel":"obj","params":[],"preview":"Math"},{"label":"Number","type":"obj","kindLabel":"obj","params":[],"preview":"Number"},{"label":"Object","type":"obj","kindLabel":"obj","params":[],"preview":"Object"},{"label":"RegExp","type":"obj","kindLabel":"obj","params":[],"preview":"RegExp"},{"label":"String","type":"obj","kindLabel":"obj","params":[],"preview":"String"},{"label":"Promise","type":"obj","kindLabel":"obj","params":[],"preview":"Promise"},{"label":"Set","type":"obj","kindLabel":"obj","params":[],"preview":"Set"},{"label":"Map","type":"obj","kindLabel":"obj","params":[],"preview":"Map"},{"label":"Symbol","type":"obj","kindLabel":"obj","params":[],"preview":"Symbol"},{"label":"WeakMap","type":"obj","kindLabel":"obj","params":[],"preview":"WeakMap"},{"label":"WeakSet","type":"obj","kindLabel":"obj","params":[],"preview":"WeakSet"},{"label":"ArrayBuffer","type":"obj","kindLabel":"obj","params":[],"preview":"ArrayBuffer"},{"label":"DataView","type":"obj","kindLabel":"obj","params":[],"preview":"DataView"},{"label":"Int32Array","type":"obj","kindLabel":"obj","params":[],"preview":"Int32Array"},{"label":"Uint32Array","type":"obj","kindLabel":"obj","params":[],"preview":"Uint32Array"},{"label":"Float32Array","type":"obj","kindLabel":"obj","params":[],"preview":"Float32Array"},{"label":"window","type":"obj","kindLabel":"obj","params":[],"preview":"window"},{"label":"document","type":"obj","kindLabel":"obj","params":[],"preview":"document"},{"label":"navigator","type":"obj","kindLabel":"obj","params":[],"preview":"navigator"},{"label":"console","type":"obj","kindLabel":"obj","params":[],"preview":"console","p5DocPath":"console"},{"label":"localStorage","type":"obj","kindLabel":"obj","params":[],"preview":"localStorage"},{"label":"sessionStorage","type":"obj","kindLabel":"obj","params":[],"preview":"sessionStorage"},{"label":"history","type":"obj","kindLabel":"obj","params":[],"preview":"history"},{"label":"location","type":"obj","kindLabel":"obj","params":[],"preview":"location"}]; diff --git a/client/utils/p5CodeAstAnalyzer.js b/client/utils/p5CodeAstAnalyzer.js index 5aaa5ec7be..3307af8c5a 100644 --- a/client/utils/p5CodeAstAnalyzer.js +++ b/client/utils/p5CodeAstAnalyzer.js @@ -24,8 +24,7 @@ let lastValidResult = { userDefinedClassMetadata: {} }; -function _p5CodeAstAnalyzer(_cm) { - const code = _cm.getValue(); +function _p5CodeAstAnalyzer(code) { let ast; try { @@ -153,8 +152,7 @@ function _p5CodeAstAnalyzer(_cm) { expr.left.object.type === 'ThisExpression' && expr.left.property.type === 'Identifier' ) { - const propName = expr.left.property.name; - classInfo.fields.add(propName); + classInfo.fields.add(expr.left.property.name); } }, @@ -165,8 +163,7 @@ function _p5CodeAstAnalyzer(_cm) { callee.object.type === 'ThisExpression' && callee.property.type === 'Identifier' ) { - const methodName = callee.property.name; - classInfo.fields.add(methodName); + classInfo.fields.add(callee.property.name); } } }, diff --git a/package-lock.json b/package-lock.json index 7866a79d9e..0f7933c165 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "p5.js-web-editor", - "version": "2.20.7", + "version": "2.20.8", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "p5.js-web-editor", - "version": "2.20.7", + "version": "2.20.8", "license": "LGPL-2.1", "dependencies": { "@auth0/s3": "^1.0.0", @@ -26,7 +26,7 @@ "acorn": "^8.14.1", "acorn-walk": "^8.3.4", "async": "^3.2.3", - "axios": "^1.13.5", + "axios": "^1.15.0", "babel-plugin-styled-components": "^1.13.2", "bcryptjs": "^2.4.3", "blob-util": "^1.2.1", @@ -66,13 +66,13 @@ "jsdom": "^20.0.0", "jshint": "^2.13.0", "jszip": "^3.10.1", - "lodash": "^4.17.23", + "lodash": "^4.18.1", "loop-protect": "github:catarak/loop-protect", "mime": "^3.0.0", "mjml": "^4.14.1", "mongodb-memory-server": "^10.2.1", "mongoose": "^8.16.3", - "nodemailer": "^7.0.11", + "nodemailer": "^8.0.5", "nodemailer-mailgun-transport": "^2.1.5", "passport": "^0.6.0", "passport-github2": "^0.1.12", @@ -13497,10 +13497,11 @@ } }, "node_modules/@storybook/core-common/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.3.tgz", + "integrity": "sha512-MCV/fYJEbqx68aE58kv2cA/kiky1G8vux3OR6/jbS+jIMe/6fJWa0DTzJU7dqijOWYwHi1t29FlfYI9uytqlpA==", "dev": true, + "license": "MIT", "dependencies": { "balanced-match": "^1.0.0" } @@ -17325,14 +17326,14 @@ } }, "node_modules/axios": { - "version": "1.13.5", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.13.5.tgz", - "integrity": "sha512-cz4ur7Vb0xS4/KUN0tPWe44eqxrIu31me+fbang3ijiNscE129POzipJJA6zniq2C/Z6sJCjMimjS8Lc/GAs8Q==", + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.15.0.tgz", + "integrity": "sha512-wWyJDlAatxk30ZJer+GeCWS209sA42X+N5jU2jy6oHTp7ufw8uzUTVFBX9+wTfAlhiJXGS0Bq7X6efruWjuK9Q==", "license": "MIT", "dependencies": { "follow-redirects": "^1.15.11", "form-data": "^4.0.5", - "proxy-from-env": "^1.1.0" + "proxy-from-env": "^2.1.0" } }, "node_modules/axobject-query": { @@ -17997,9 +17998,10 @@ "integrity": "sha512-AlcaJBi/pqqJBIQ8U9Mcpc9i8Aqxn88Skv5d+xBX006BY5u8N3mGLHa5Lgppa7L/HfwgwLgZ6NYs+Ag6uUmJRA==" }, "node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.13.tgz", + "integrity": "sha512-9ZLprWS6EENmhEOpjCYW2c8VkmOvckIJZfkr7rBW6dObmfgJ/L1GpSYW5Hpo9lDz4D1+n0Ckz8rU7FwHDQiG/w==", + "license": "MIT", "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -23315,9 +23317,9 @@ } }, "node_modules/flatted": { - "version": "3.4.1", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.1.tgz", - "integrity": "sha512-IxfVbRFVlV8V/yRaGzk0UVIcsKKHMSfYw66T/u4nTwlWteQePsxe//LjudR1AMX4tZW3WFCh3Zqa/sjlqpbURQ==", + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz", + "integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==", "license": "ISC" }, "node_modules/flatten": { @@ -23913,10 +23915,11 @@ } }, "node_modules/handlebars": { - "version": "4.7.8", - "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.8.tgz", - "integrity": "sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ==", + "version": "4.7.9", + "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.9.tgz", + "integrity": "sha512-4E71E0rpOaQuJR2A3xDZ+GM1HyWYv1clR58tC8emQNeQe3RH7MAzSbat+V0wG78LQBo6m6bzSG/L4pBuCsgnUQ==", "dev": true, + "license": "MIT", "dependencies": { "minimist": "^1.2.5", "neo-async": "^2.6.2", @@ -27753,9 +27756,10 @@ } }, "node_modules/js-beautify/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.3.tgz", + "integrity": "sha512-MCV/fYJEbqx68aE58kv2cA/kiky1G8vux3OR6/jbS+jIMe/6fJWa0DTzJU7dqijOWYwHi1t29FlfYI9uytqlpA==", + "license": "MIT", "dependencies": { "balanced-match": "^1.0.0" } @@ -28027,6 +28031,12 @@ "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=" }, + "node_modules/jshint/node_modules/lodash": { + "version": "4.17.23", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.23.tgz", + "integrity": "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==", + "license": "MIT" + }, "node_modules/jshint/node_modules/readable-stream": { "version": "1.1.14", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", @@ -28473,9 +28483,9 @@ } }, "node_modules/lodash": { - "version": "4.17.23", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.23.tgz", - "integrity": "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==", + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz", + "integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==", "license": "MIT" }, "node_modules/lodash._reinterpolate": { @@ -28522,10 +28532,12 @@ "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==" }, "node_modules/lodash.template": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/lodash.template/-/lodash.template-4.5.0.tgz", - "integrity": "sha512-84vYFxIkmidUiFxidA/KjjH9pAycqW+h980j7Fuz5qxRtO9pgB7MDFTdys1N7A5mcucRiDyEq4fusljItR1T/A==", + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/lodash.template/-/lodash.template-4.18.1.tgz", + "integrity": "sha512-5urZrLnV/VD6zHK5KsVtZgt7H19v51mIzoS0aBNH8yp3I8tbswrEjOABOPY8m8uB7NuibubLrMX+Y0PXsU9X+w==", + "deprecated": "This package is deprecated. Use https://socket.dev/npm/package/eta instead.", "dev": true, + "license": "MIT", "dependencies": { "lodash._reinterpolate": "^3.0.0", "lodash.templatesettings": "^4.0.0" @@ -31309,9 +31321,9 @@ "integrity": "sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==" }, "node_modules/nodemailer": { - "version": "7.0.11", - "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-7.0.11.tgz", - "integrity": "sha512-gnXhNRE0FNhD7wPSCGhdNh46Hs6nm+uTyg+Kq0cZukNQiYdnCsoQjodNP9BQVG9XrcK/v6/MgpAPBUFyzh9pvw==", + "version": "8.0.5", + "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-8.0.5.tgz", + "integrity": "sha512-0PF8Yb1yZuQfQbq+5/pZJrtF6WQcjTd5/S4JOHs9PGFxuTqoB/icwuB44pOdURHJbRKX1PPoJZtY7R4VUoCC8w==", "license": "MIT-0", "engines": { "node": ">=6.0.0" @@ -32195,9 +32207,9 @@ } }, "node_modules/path-to-regexp": { - "version": "0.1.12", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", - "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==", + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.13.tgz", + "integrity": "sha512-A/AGNMFN3c8bOlvV9RreMdrv7jsmF9XIfDeCd87+I8RNg6s78BhJxMu69NEMHBSJFxKidViTEdruRwEk/WIKqA==", "license": "MIT" }, "node_modules/pathval": { @@ -32226,9 +32238,10 @@ "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==" }, "node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", + "license": "MIT", "engines": { "node": ">=8.6" }, @@ -33791,9 +33804,13 @@ } }, "node_modules/proxy-from-env": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", - "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-2.1.0.tgz", + "integrity": "sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA==", + "license": "MIT", + "engines": { + "node": ">=10" + } }, "node_modules/pseudomap": { "version": "1.0.2", @@ -38515,9 +38532,10 @@ "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=" }, "node_modules/yaml": { - "version": "1.10.2", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", - "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", + "version": "1.10.3", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.3.tgz", + "integrity": "sha512-vIYeF1u3CjlhAFekPPAk2h/Kv4T3mAkMox5OymRiJQB0spDP10LHvt+K7G9Ny6NuuMAb25/6n1qyUjAcGNf/AA==", + "license": "ISC", "engines": { "node": ">= 6" } @@ -48770,9 +48788,9 @@ } }, "brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.3.tgz", + "integrity": "sha512-MCV/fYJEbqx68aE58kv2cA/kiky1G8vux3OR6/jbS+jIMe/6fJWa0DTzJU7dqijOWYwHi1t29FlfYI9uytqlpA==", "dev": true, "requires": { "balanced-match": "^1.0.0" @@ -51681,13 +51699,13 @@ "dev": true }, "axios": { - "version": "1.13.5", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.13.5.tgz", - "integrity": "sha512-cz4ur7Vb0xS4/KUN0tPWe44eqxrIu31me+fbang3ijiNscE129POzipJJA6zniq2C/Z6sJCjMimjS8Lc/GAs8Q==", + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.15.0.tgz", + "integrity": "sha512-wWyJDlAatxk30ZJer+GeCWS209sA42X+N5jU2jy6oHTp7ufw8uzUTVFBX9+wTfAlhiJXGS0Bq7X6efruWjuK9Q==", "requires": { "follow-redirects": "^1.15.11", "form-data": "^4.0.5", - "proxy-from-env": "^1.1.0" + "proxy-from-env": "^2.1.0" } }, "axobject-query": { @@ -52188,9 +52206,9 @@ "integrity": "sha512-AlcaJBi/pqqJBIQ8U9Mcpc9i8Aqxn88Skv5d+xBX006BY5u8N3mGLHa5Lgppa7L/HfwgwLgZ6NYs+Ag6uUmJRA==" }, "brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.13.tgz", + "integrity": "sha512-9ZLprWS6EENmhEOpjCYW2c8VkmOvckIJZfkr7rBW6dObmfgJ/L1GpSYW5Hpo9lDz4D1+n0Ckz8rU7FwHDQiG/w==", "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -56016,9 +56034,9 @@ } }, "flatted": { - "version": "3.4.1", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.1.tgz", - "integrity": "sha512-IxfVbRFVlV8V/yRaGzk0UVIcsKKHMSfYw66T/u4nTwlWteQePsxe//LjudR1AMX4tZW3WFCh3Zqa/sjlqpbURQ==" + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz", + "integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==" }, "flatten": { "version": "1.0.2", @@ -56434,9 +56452,9 @@ "dev": true }, "handlebars": { - "version": "4.7.8", - "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.8.tgz", - "integrity": "sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ==", + "version": "4.7.9", + "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.9.tgz", + "integrity": "sha512-4E71E0rpOaQuJR2A3xDZ+GM1HyWYv1clR58tC8emQNeQe3RH7MAzSbat+V0wG78LQBo6m6bzSG/L4pBuCsgnUQ==", "dev": true, "requires": { "minimist": "^1.2.5", @@ -59160,9 +59178,9 @@ }, "dependencies": { "brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.3.tgz", + "integrity": "sha512-MCV/fYJEbqx68aE58kv2cA/kiky1G8vux3OR6/jbS+jIMe/6fJWa0DTzJU7dqijOWYwHi1t29FlfYI9uytqlpA==", "requires": { "balanced-match": "^1.0.0" } @@ -59369,6 +59387,11 @@ "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=" }, + "lodash": { + "version": "4.17.23", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.23.tgz", + "integrity": "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==" + }, "readable-stream": { "version": "1.1.14", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", @@ -59711,9 +59734,9 @@ } }, "lodash": { - "version": "4.17.23", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.23.tgz", - "integrity": "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==" + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz", + "integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==" }, "lodash._reinterpolate": { "version": "3.0.0", @@ -59759,9 +59782,9 @@ "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==" }, "lodash.template": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/lodash.template/-/lodash.template-4.5.0.tgz", - "integrity": "sha512-84vYFxIkmidUiFxidA/KjjH9pAycqW+h980j7Fuz5qxRtO9pgB7MDFTdys1N7A5mcucRiDyEq4fusljItR1T/A==", + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/lodash.template/-/lodash.template-4.18.1.tgz", + "integrity": "sha512-5urZrLnV/VD6zHK5KsVtZgt7H19v51mIzoS0aBNH8yp3I8tbswrEjOABOPY8m8uB7NuibubLrMX+Y0PXsU9X+w==", "dev": true, "requires": { "lodash._reinterpolate": "^3.0.0", @@ -61784,9 +61807,9 @@ "integrity": "sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==" }, "nodemailer": { - "version": "7.0.11", - "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-7.0.11.tgz", - "integrity": "sha512-gnXhNRE0FNhD7wPSCGhdNh46Hs6nm+uTyg+Kq0cZukNQiYdnCsoQjodNP9BQVG9XrcK/v6/MgpAPBUFyzh9pvw==" + "version": "8.0.5", + "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-8.0.5.tgz", + "integrity": "sha512-0PF8Yb1yZuQfQbq+5/pZJrtF6WQcjTd5/S4JOHs9PGFxuTqoB/icwuB44pOdURHJbRKX1PPoJZtY7R4VUoCC8w==" }, "nodemailer-mailgun-transport": { "version": "2.1.5", @@ -62434,9 +62457,9 @@ } }, "path-to-regexp": { - "version": "0.1.12", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", - "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==" + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.13.tgz", + "integrity": "sha512-A/AGNMFN3c8bOlvV9RreMdrv7jsmF9XIfDeCd87+I8RNg6s78BhJxMu69NEMHBSJFxKidViTEdruRwEk/WIKqA==" }, "pathval": { "version": "2.0.1", @@ -62460,9 +62483,9 @@ "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==" }, "picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==" + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==" }, "pify": { "version": "4.0.1", @@ -63617,9 +63640,9 @@ } }, "proxy-from-env": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", - "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-2.1.0.tgz", + "integrity": "sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA==" }, "pseudomap": { "version": "1.0.2", @@ -67093,9 +67116,9 @@ "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=" }, "yaml": { - "version": "1.10.2", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", - "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==" + "version": "1.10.3", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.3.tgz", + "integrity": "sha512-vIYeF1u3CjlhAFekPPAk2h/Kv4T3mAkMox5OymRiJQB0spDP10LHvt+K7G9Ny6NuuMAb25/6n1qyUjAcGNf/AA==" }, "yargs": { "version": "16.2.0", diff --git a/package.json b/package.json index f0ef304515..e8acfeca05 100644 --- a/package.json +++ b/package.json @@ -66,7 +66,12 @@ "displayName": "client", "testEnvironment": "jsdom", "transform": { - "^.+\\.[jt]sx?$": "babel-jest" + "\\.[jt]sx?$": [ + "babel-jest", + { + "configFile": "./.babelrc" + } + ] }, "moduleFileExtensions": [ "ts", @@ -83,6 +88,9 @@ "^.+\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)(|\\?byContent|\\?byUrl)$": "/client/__mocks__/fileMock.js", "\\.(css|less|scss)$": "/client/__mocks__/styleMock.js" }, + "transformIgnorePatterns": [ + "/node_modules/(?!colors-named|colors-named-hex|hsl-matcher|@emmetio/)" + ], "testMatch": [ "/client/**/*.test.(js|jsx|ts|tsx)" ] @@ -145,6 +153,7 @@ "@types/nodemailer": "^7.0.1", "@types/nodemailer-mailgun-transport": "^1.4.6", "@types/passport": "^1.0.17", + "@types/prettier": "^2.7.3", "@types/react": "^16.14.0", "@types/react-dom": "^16.9.25", "@types/react-helmet": "^6.1.11", @@ -194,7 +203,7 @@ "storybook-addon-theme-playground": "^3.1.0", "style-loader": "^3.3.4", "terser-webpack-plugin": "^5.3.1", - "typescript": "^5.8.3", + "typescript": "^5.1.6", "webpack-cli": "^4.9.2", "webpack-manifest-plugin": "^5.0.0", "webpack-node-externals": "^3.0.0" @@ -210,8 +219,23 @@ "@babel/parser": "^7.27.5", "@babel/register": "^7.14.5", "@babel/traverse": "^7.27.4", + "@codemirror/autocomplete": "^6.18.6", + "@codemirror/commands": "^6.8.1", + "@codemirror/lang-css": "^6.3.1", + "@codemirror/lang-html": "^6.4.9", + "@codemirror/lang-javascript": "^6.2.3", + "@codemirror/lang-json": "^6.0.1", + "@codemirror/lang-xml": "^6.1.0", + "@codemirror/language": "^6.11.0", + "@codemirror/lint": "^6.8.5", + "@codemirror/search": "^6.5.11", + "@codemirror/state": "^6.5.2", + "@codemirror/view": "^6.40.0", + "@connieye/codemirror-color-picker": "^1.0.3", "@emmetio/codemirror-plugin": "^1.2.4", + "@emmetio/codemirror6-plugin": "^0.4.0", "@gatsbyjs/webpack-hot-middleware": "^2.25.3", + "@lezer/highlight": "^1.2.3", "@pmmmwh/react-refresh-webpack-plugin": "^0.5.11", "@redux-devtools/core": "^3.11.0", "@redux-devtools/dock-monitor": "^3.0.1", @@ -229,8 +253,7 @@ "bson-objectid": "^2.0.3", "classnames": "^2.3.1", "clipboard": "^1.7.1", - "codemirror": "^5.62.0", - "codemirror-colorpicker": "^1.9.72", + "codemirror": "^6.0.1", "connect-mongo": "^5.1.0", "console-feed": "^3.2.0", "cookie-parser": "^1.4.5", @@ -243,6 +266,7 @@ "dotenv": "^2.0.0", "dropzone": "^4.3.0", "escape-string-regexp": "^1.0.5", + "eslint-linter-browserify": "^10.0.3", "eslint-scope": "^8.4.0", "eslint-webpack-plugin": "^3.1.1", "express": "^4.22.1", @@ -252,7 +276,7 @@ "friendly-words": "^1.2.1", "fuse.js": "^6.6.2", "history": "^4.10.1", - "htmlhint": "^0.15.1", + "htmlhint": "^0.15.2", "i18next": "^19.9.2", "i18next-http-backend": "^1.2.6", "is-url": "^1.2.4", diff --git a/server/controllers/collection.controller/listCollections.js b/server/controllers/collection.controller/listCollections.js index e1ee11d28f..ead375c5b1 100644 --- a/server/controllers/collection.controller/listCollections.js +++ b/server/controllers/collection.controller/listCollections.js @@ -19,10 +19,34 @@ export default async function listCollections(req, res) { res.status(code).json({ success: false, message }); }; - const sendSuccess = (collections) => { - res.status(200).json(collections); + const sendSuccess = (payload) => { + res.status(200).json(payload); }; + const parsePositiveInt = (value, fallback) => { + const parsed = Number.parseInt(String(value), 10); + if (Number.isFinite(parsed) && parsed > 0) return parsed; + return fallback; + }; + + const coerceSortDir = (value) => { + const v = String(value || '').toLowerCase(); + return v === 'asc' ? 'asc' : 'desc'; + }; + + const coerceSortField = (value) => { + const allowed = new Set(['updatedAt', 'createdAt', 'name']); + const v = String(value || ''); + return allowed.has(v) ? v : 'updatedAt'; + }; + + const shouldPaginate = () => + typeof req.query.page !== 'undefined' || + typeof req.query.limit !== 'undefined' || + typeof req.query.sortField !== 'undefined' || + typeof req.query.sortDir !== 'undefined' || + typeof req.query.q !== 'undefined'; + try { const ownerId = await getOwnerUserId(req); @@ -30,37 +54,85 @@ export default async function listCollections(req, res) { return sendFailure({ code: 404, message: 'User not found' }); } - const collections = await Collection.find({ owner: ownerId }).populate([ + const page = parsePositiveInt(req.query.page, 1); + const limit = parsePositiveInt(req.query.limit, 10); + const sortField = coerceSortField(req.query.sortField); + const sortDir = coerceSortDir(req.query.sortDir); + const q = String(req.query.q || '').trim(); + + const query = { owner: ownerId }; + if (q) { + query.name = { $regex: q, $options: 'i' }; + } + + const baseFind = Collection.find(query).populate([ { path: 'owner', select: ['id', 'username'] }, { path: 'items.project', select: ['id', 'name', 'slug', 'visibility'], - populate: { - path: 'user', - select: ['username'] - } + populate: { path: 'user', select: ['username'] } } ]); const isOwner = req.user && req.user._id.equals(ownerId); - if (isOwner) { - return sendSuccess(collections); + if (!shouldPaginate()) { + const collections = await baseFind.exec(); + + if (isOwner) { + return sendSuccess(collections); + } + + const publicCollections = collections.map((collection) => { + const { items: originalItems } = collection; + const items = originalItems.filter( + (item) => item.project && item.project.visibility === 'Public' + ); + return { + ...collection.toObject(), + items, + id: collection._id + }; + }); + + return sendSuccess(publicCollections); } - const publicCollections = collections.map((collection) => { - const { items: originalItems } = collection; - const items = originalItems.filter( - (item) => item.project && item.project.visibility === 'Public' - ); - return { - ...collection.toObject(), - items, - id: collection._id - }; - }); + const totalCollections = await Collection.countDocuments(query); + const totalPages = Math.max(1, Math.ceil(totalCollections / limit)); + const safePage = Math.min(page, totalPages); + const skip = (safePage - 1) * limit; - return sendSuccess(publicCollections); + const collections = await baseFind + .sort({ [sortField]: sortDir }) + .skip(skip) + .limit(limit) + .exec(); + + const normalizedCollections = isOwner + ? collections + : collections.map((collection) => { + const { items: originalItems } = collection; + const items = originalItems.filter( + (item) => item.project && item.project.visibility === 'Public' + ); + return { + ...collection.toObject(), + items, + id: collection._id + }; + }); + + return sendSuccess({ + collections: normalizedCollections, + metadata: { + page: safePage, + totalPages, + totalCollections, + limit, + hasPagination: totalPages > 1 + } + }); } catch (error) { return sendFailure({ code: error.code || 500, diff --git a/server/scripts/update-p5-hinter.js b/server/scripts/update-p5-hinter.js index a6a667eac4..d438c28ca0 100644 --- a/server/scripts/update-p5-hinter.js +++ b/server/scripts/update-p5-hinter.js @@ -2,81 +2,90 @@ const fs = require('fs'); const process = require('process'); const axios = require('axios'); -// const getDescription = (d) => { -// return d.split('\n')[0].replace('

    ', ''); -// }; - +// TODO: Currently this makes duplicate entries because +// the default Javascript hinter also has these, +// but should we keep them around for the p5 reference links? const reservedKeywords = [ - { name: 'await', p5DocPath: false }, - { name: 'break', p5DocPath: false }, - { name: 'case', p5DocPath: false }, - { name: 'catch', p5DocPath: false }, + { name: 'await', p5DocPath: undefined }, { name: 'class', p5DocPath: 'class' }, { name: 'const', p5DocPath: 'const' }, - { name: 'continue', p5DocPath: false }, - { name: 'debugger', p5DocPath: false }, - { name: 'default', p5DocPath: false }, - { name: 'delete', p5DocPath: false }, - { name: 'do', p5DocPath: false }, { name: 'else', p5DocPath: 'if-else' }, - { name: 'export', p5DocPath: false }, - { name: 'extends', p5DocPath: false }, - { name: 'finally', p5DocPath: false }, + { name: 'export', p5DocPath: undefined }, { name: 'for', p5DocPath: 'for' }, { name: 'function', p5DocPath: 'function' }, { name: 'if', p5DocPath: 'if-else' }, - { name: 'import', p5DocPath: false }, - { name: 'in', p5DocPath: false }, - { name: 'instanceof', p5DocPath: false }, - { name: 'new', p5DocPath: false }, { name: 'return', p5DocPath: 'return' }, - { name: 'super', p5DocPath: false }, - { name: 'switch', p5DocPath: false }, - { name: 'this', p5DocPath: false }, - { name: 'throw', p5DocPath: false }, - { name: 'try', p5DocPath: false }, - { name: 'typeof', p5DocPath: false }, - { name: 'var', p5DocPath: false }, - { name: 'void', p5DocPath: false }, { name: 'while', p5DocPath: 'while' }, - { name: 'with', p5DocPath: false }, - { name: 'yield', p5DocPath: false }, + { name: 'with', p5DocPath: undefined }, { name: 'let', p5DocPath: 'let' } ]; const reservedObjects = [ - { name: 'Array', p5DocPath: false }, - { name: 'Boolean', p5DocPath: false }, - { name: 'Date', p5DocPath: false }, - { name: 'Error', p5DocPath: false }, - { name: 'Function', p5DocPath: false }, + { name: 'Array', p5DocPath: undefined }, + { name: 'Boolean', p5DocPath: undefined }, + { name: 'Date', p5DocPath: undefined }, + { name: 'Error', p5DocPath: undefined }, + { name: 'Function', p5DocPath: undefined }, { name: 'JSON', p5DocPath: 'JSON' }, - { name: 'Math', p5DocPath: false }, - { name: 'Number', p5DocPath: false }, - { name: 'Object', p5DocPath: false }, - { name: 'RegExp', p5DocPath: false }, - { name: 'String', p5DocPath: false }, - { name: 'Promise', p5DocPath: false }, - { name: 'Set', p5DocPath: false }, - { name: 'Map', p5DocPath: false }, - { name: 'Symbol', p5DocPath: false }, - { name: 'WeakMap', p5DocPath: false }, - { name: 'WeakSet', p5DocPath: false }, - { name: 'ArrayBuffer', p5DocPath: false }, - { name: 'DataView', p5DocPath: false }, - { name: 'Int32Array', p5DocPath: false }, - { name: 'Uint32Array', p5DocPath: false }, - { name: 'Float32Array', p5DocPath: false }, - { name: 'window', p5DocPath: false }, - { name: 'document', p5DocPath: false }, - { name: 'navigator', p5DocPath: false }, + { name: 'Math', p5DocPath: undefined }, + { name: 'Number', p5DocPath: undefined }, + { name: 'Object', p5DocPath: undefined }, + { name: 'RegExp', p5DocPath: undefined }, + { name: 'String', p5DocPath: undefined }, + { name: 'Promise', p5DocPath: undefined }, + { name: 'Set', p5DocPath: undefined }, + { name: 'Map', p5DocPath: undefined }, + { name: 'Symbol', p5DocPath: undefined }, + { name: 'WeakMap', p5DocPath: undefined }, + { name: 'WeakSet', p5DocPath: undefined }, + { name: 'ArrayBuffer', p5DocPath: undefined }, + { name: 'DataView', p5DocPath: undefined }, + { name: 'Int32Array', p5DocPath: undefined }, + { name: 'Uint32Array', p5DocPath: undefined }, + { name: 'Float32Array', p5DocPath: undefined }, + { name: 'window', p5DocPath: undefined }, + { name: 'document', p5DocPath: undefined }, + { name: 'navigator', p5DocPath: undefined }, { name: 'console', p5DocPath: 'console' }, - { name: 'localStorage', p5DocPath: false }, - { name: 'sessionStorage', p5DocPath: false }, - { name: 'history', p5DocPath: false }, - { name: 'location', p5DocPath: false } + { name: 'localStorage', p5DocPath: undefined }, + { name: 'sessionStorage', p5DocPath: undefined }, + { name: 'history', p5DocPath: undefined }, + { name: 'location', p5DocPath: undefined } ]; +function getKindLabel(type) { + switch (type) { + case 'method': + return 'fun'; + case 'variable': + return 'var'; + case 'constant': + return 'const'; + case 'keyword': + return 'kw'; + case 'boolean': + return 'bool'; + case 'obj': + return 'obj'; + default: + return type; + } +} + +// create ghost text preview for methods +function makePreview(label, type, params = []) { + const formattedParams = params + .map((param) => (param.o ? `[${param.p}]` : param.p)) + .join(', '); + + if (type === 'method') { + return `${label}(${formattedParams})`; + } + + return label; +} + +// TODO: add back in reference version switching depending user's p5.js version axios .get('https://p5js.org/reference/data.json') .then((response) => { @@ -92,49 +101,60 @@ axios obj.name && obj.itemtype ) { - let type; + let itemType; let params = []; if (obj.itemtype === 'method') { - type = 'fun'; + itemType = 'method'; params = obj.params?.map((param) => ({ p: param.name, // param name o: param.optional ?? false // optional })); } else if (obj.itemtype === 'property') { - type = 'var'; - } else type = 'attr'; + itemType = obj.module === 'Constants' ? 'constant' : 'variable'; + } else itemType = 'attr'; p5Keywords.push({ - text: obj.name, - type, + label: obj.name, + type: itemType, + kindLabel: getKindLabel(itemType), params, - p5: true + preview: makePreview(obj.name, itemType, params), + p5DocPath: obj.name }); } }); ['true', 'false'].forEach((bol) => { p5Keywords.push({ - text: bol, + label: bol, type: 'boolean', - p5: 'boolean' + kindLabel: 'bool', + params: [], + preview: bol, + p5DocPath: 'boolean' }); }); reservedKeywords.forEach((keyword) => { p5Keywords.push({ - text: keyword.name, + label: keyword.name, type: 'keyword', - p5: keyword.p5DocPath + kindLabel: 'keyword', + params: [], + preview: keyword.name, + p5DocPath: keyword.p5DocPath }); }); reservedObjects.forEach((keyword) => { p5Keywords.push({ - text: keyword.name, + label: keyword.name, type: 'obj', - p5: keyword.p5DocPath + kindLabel: 'obj', + params: [], + preview: keyword.name, + p5DocPath: keyword.p5DocPath }); }); diff --git a/translations/locales/bn/translations.json b/translations/locales/bn/translations.json index 613fdb67b2..8aa2fc4786 100644 --- a/translations/locales/bn/translations.json +++ b/translations/locales/bn/translations.json @@ -169,6 +169,7 @@ "Settings": "সেটিংস", "GeneralSettings": "সাধারণ সেটিংস", "Accessibility": "ব্যবহারযোগ্যতা", + "LibraryManagement": "লাইব্রেরি ম্যানেজমেন্ট", "Theme": "থিম", "LightTheme": "সাদা", "LightThemeARIA": "সাদা থিম চালু", @@ -472,6 +473,7 @@ "AddSketch": "স্কেচ যোগ করুন", "DeleteFromCollection": "{{name_sketch}} কে এই সংগ্রহ থেকে সরাতে চান কি না?", "SketchDeleted": "স্কেচ ডিলিট হয়েছে", + "SketchRemoveLabel": "Remove from Collection", "SketchRemoveARIA": "সংগ্রহ থেকে স্কেচ রিমুভ করুন", "DescriptionPlaceholder": "বর্ণনা যোগ করুন", "NumSketches": "{{count}} স্কেচ", diff --git a/translations/locales/de/translations.json b/translations/locales/de/translations.json index b05ed9c6ba..d05907f1ce 100644 --- a/translations/locales/de/translations.json +++ b/translations/locales/de/translations.json @@ -432,6 +432,7 @@ "AddSketch": "Sketch hinzufügen", "DeleteFromCollection": "Bist Du sicher, dass Du {{name_sketch}} aus dieser Sammlung entfernen willst?", "SketchDeleted": "Sketch gelöscht", + "SketchRemoveLabel": "Remove from Collection", "SketchRemoveARIA": "Sketch aus der Sammlung entfernen", "DescriptionPlaceholder": "Beschreibung hinzufügen", "Description": "Beschreibung", diff --git a/translations/locales/en-US/translations.json b/translations/locales/en-US/translations.json index 7529a1d9ff..a1c17007c8 100644 --- a/translations/locales/en-US/translations.json +++ b/translations/locales/en-US/translations.json @@ -516,6 +516,7 @@ "DeleteFromCollection": "Are you sure you want to remove {{name_sketch}} from this collection?", "SketchDeleted": "Sketch deleted", "SketchRemoveARIA": "Remove sketch from collection", + "SketchRemoveLabel": "Remove from Collection", "DescriptionPlaceholder": "Add description", "Description": "description", "NumSketches": "{{count}} sketch", diff --git a/translations/locales/es-419/translations.json b/translations/locales/es-419/translations.json index 3880322a65..4a7e80dbcc 100644 --- a/translations/locales/es-419/translations.json +++ b/translations/locales/es-419/translations.json @@ -442,6 +442,7 @@ "AddSketch": "Agregar Bosquejo", "DeleteFromCollection": "¿Estás seguro que quieres remover {{name_sketch}} de esta colección?", "SketchDeleted": "El bosquejo fue eliminado", + "SketchRemoveLabel": "Remove from Collection", "SketchRemoveARIA": "Remover bosquejo de la colección", "DescriptionPlaceholder": "Agregar descripción", "Description": "descripción", diff --git a/translations/locales/fr-CA/translations.json b/translations/locales/fr-CA/translations.json index dab5114c54..50730b6c3c 100644 --- a/translations/locales/fr-CA/translations.json +++ b/translations/locales/fr-CA/translations.json @@ -446,6 +446,7 @@ "AddSketch": "Ajouter un croquis", "DeleteFromCollection": "Êtes-vous sûr de vouloir supprimer {{name_sketch}} de cette collection?", "SketchDeleted": "Croquis supprimé", + "SketchRemoveLabel": "Remove from Collection", "SketchRemoveARIA": "Supprimer le croquis de la collection", "DescriptionPlaceholder": "Ajouter une description", "Description": "description", diff --git a/translations/locales/hi/translations.json b/translations/locales/hi/translations.json index 49f4af1515..bb0355cbf0 100644 --- a/translations/locales/hi/translations.json +++ b/translations/locales/hi/translations.json @@ -226,10 +226,10 @@ "TableOutputARIA": "टेबल आउटपुट चालू", "LibraryVersion": "p5.js संस्करण", "LibraryVersionInfo": "p5.js का एक [नया 2.0 संस्करण](https://github.com/processing/p5.js/releases/) उपलब्ध है! यह अगस्त 2026 में डिफ़ॉल्ट बन जाएगा, इसलिए इस समय का उपयोग इसे आज़माने और बग्स की रिपोर्ट करने के लिए करें। क्या आप 1.x से 2.0 में स्केच को स्थानांतरित करने में रुचि रखते हैं? [संगतता और स्थानांतरण संसाधनों](https://github.com/processing/p5.js-compatibility) को देखें।", - "SoundAddon": "p5.sound.js Add-on लाइब्रेरी", - "PreloadAddon": "p5.js 1.x Compatibility Add-on लाइब्रेरी — प्रीलोड", - "ShapesAddon": "p5.js 1.x Compatibility Add-on लाइब्रेरी — आकार", - "DataAddon": "p5.js 1.x Compatibility Add-on लाइब्रेरी — डेटा संरचनाएँ" + "SoundAddon": "p5.sound.js ऐड-ऑन लाइब्रेरी", + "PreloadAddon": "p5.js 1.x संगतता ऐड-ऑन लाइब्रेरी — प्रीलोड", + "ShapesAddon": "p5.js 1.x संगतता ऐड-ऑन लाइब्रेरी — आकार", + "DataAddon": "p5.js 1.x संगतता ऐड-ऑन लाइब्रेरी — डेटा संरचनाएँ" }, "KeyboardShortcuts": { "Title": " कीबोर्ड शॉर्टकट", @@ -510,6 +510,7 @@ "AddSketch": "स्केच जोड़ें", "DeleteFromCollection": "क्या आप वाकई {{name_sketch}} को इस संग्रह से हटाना चाहते हो?", "SketchDeleted": "स्केच डिलीट किया", + "SketchRemoveLabel": "Remove from Collection", "SketchRemoveARIA": "स्केच संग्रह से हटाएं", "DescriptionPlaceholder": "विवरण लिखें", "NumSketches": "{{count}} स्केच", diff --git a/translations/locales/it/translations.json b/translations/locales/it/translations.json index 4dfb4ac25a..1ff103e66c 100644 --- a/translations/locales/it/translations.json +++ b/translations/locales/it/translations.json @@ -445,6 +445,7 @@ "AddSketch": "Aggiungi sketch", "DeleteFromCollection": "Sei sicuro di voler cancellare {{name_sketch}} dalla collezione?", "SketchDeleted": "Sketch cancellato", + "SketchRemoveLabel": "Remove from Collection", "SketchRemoveARIA": "Cancella sketch dalla collezione", "DescriptionPlaceholder": "Aggiungi descrizione", "Description": "descrizione", diff --git a/translations/locales/ja/translations.json b/translations/locales/ja/translations.json index eda22c412e..5200507190 100644 --- a/translations/locales/ja/translations.json +++ b/translations/locales/ja/translations.json @@ -436,6 +436,7 @@ "AddSketch": "スケッチを追加する", "DeleteFromCollection": "このコレクションから {{name_sketch}} を削除してもよろしいですか?", "SketchDeleted": "スケッチが削除されました", + "SketchRemoveLabel": "Remove from Collection", "SketchRemoveARIA": "コレクションからスケッチを削除する", "DescriptionPlaceholder": "スケッチについて記述する", "Description": "コレクションについて", diff --git a/translations/locales/ko/translations.json b/translations/locales/ko/translations.json index 7f915a71ff..972e048a8d 100644 --- a/translations/locales/ko/translations.json +++ b/translations/locales/ko/translations.json @@ -424,6 +424,7 @@ "AddSketch": "스케치 추가하기 Add Sketch", "DeleteFromCollection": "Are you sure you want to remove {{name_sketch}} from this collection?", "SketchDeleted": "Sketch deleted", + "SketchRemoveLabel": "Remove from Collection", "SketchRemoveARIA": "Remove sketch from collection", "DescriptionPlaceholder": "Add description", "Description": "description", diff --git a/translations/locales/ne/translations.json b/translations/locales/ne/translations.json index 4d61693c08..f0306847ec 100644 --- a/translations/locales/ne/translations.json +++ b/translations/locales/ne/translations.json @@ -507,6 +507,7 @@ "AddSketch": "स्केच थप्नुहोस्", "DeleteFromCollection": "के तपाईं पक्का यो कलेक्सनबाट {{name_sketch}} हटाउन चाहनुहुन्छ?", "SketchDeleted": "स्केच डिलिट भयो", + "SketchRemoveLabel": "Remove from Collection", "SketchRemoveARIA": "कलेक्सनबाट स्केच हटाउनुहोस्", "DescriptionPlaceholder": "विवरण थप्नुहोस्", "Description": "विवरण", diff --git a/translations/locales/pt-BR/translations.json b/translations/locales/pt-BR/translations.json index 2fdfd4bef1..df49836ae9 100644 --- a/translations/locales/pt-BR/translations.json +++ b/translations/locales/pt-BR/translations.json @@ -475,6 +475,7 @@ "AddSketch": "Adicionar Esboço", "DeleteFromCollection": "Realmente quer remover {{name_sketch}} dessa coleção?", "SketchDeleted": "Esboço apagado", + "SketchRemoveLabel": "Remove from Collection", "SketchRemoveARIA": "Remover esboço da coleção", "DescriptionPlaceholder": "Adicionar descrição", "NumSketches": "{{count}} esboço(s)", diff --git a/translations/locales/sv/translations.json b/translations/locales/sv/translations.json index e72edd835f..b96a08d3ed 100644 --- a/translations/locales/sv/translations.json +++ b/translations/locales/sv/translations.json @@ -435,6 +435,7 @@ "AddSketch": "Lägg till sketch", "DeleteFromCollection": "Är du säker på att du vill radera {{name_sketch}} från den här samlingen?", "SketchDeleted": "Sketchen är raderad", + "SketchRemoveLabel": "Remove from Collection", "SketchRemoveARIA": "Radera sketch från samling", "DescriptionPlaceholder": "Lägg till beskrivning", "Description": "beskrivning", diff --git a/translations/locales/tr/translations.json b/translations/locales/tr/translations.json index c66c561e68..8eb599f91f 100644 --- a/translations/locales/tr/translations.json +++ b/translations/locales/tr/translations.json @@ -439,6 +439,7 @@ "AddSketch": "Eskiz ekle", "DeleteFromCollection": "{{name_sketch}} eskizini koleksiyondan kaldırmak istediğinizden emin misiniz?", "SketchDeleted": "Eskiz silindi", + "SketchRemoveLabel": "Remove from Collection", "SketchRemoveARIA": "Eskiz koleksiyondan kaldır", "DescriptionPlaceholder": "Açıklama ekle", "Description": "açıklama", diff --git a/translations/locales/uk-UA/translations.json b/translations/locales/uk-UA/translations.json index b254d11387..9101e500d3 100644 --- a/translations/locales/uk-UA/translations.json +++ b/translations/locales/uk-UA/translations.json @@ -497,6 +497,7 @@ "AddSketch": "Додати скетч", "DeleteFromCollection": "Ви впевнені, що хочете видалити {{name_sketch}} з цієї колекції?", "SketchDeleted": "Скетч видалено", + "SketchRemoveLabel": "Remove from Collection", "SketchRemoveARIA": "Видалити скетч із колекції", "DescriptionPlaceholder": "Додати опис", "Description": "опис", diff --git a/translations/locales/ur/translations.json b/translations/locales/ur/translations.json index c6f90d5abf..85e8f0b6cf 100644 --- a/translations/locales/ur/translations.json +++ b/translations/locales/ur/translations.json @@ -436,6 +436,7 @@ "AddSketch": "خاکہ شامل کریں۔", "DeleteFromCollection": "کیا آپ واقعی ہٹانا چاہتے ہیں {{name_sketch}} اس مجموعہ سے؟", "SketchDeleted": "خاکہ حذف کر دیا گیا۔", + "SketchRemoveLabel": "Remove from Collection", "SketchRemoveARIA": "مجموعہ سے خاکہ کو ہٹا دیں۔", "DescriptionPlaceholder": "تفصیل شامل کریں۔", "Description": "تفصیل", diff --git a/translations/locales/zh-CN/translations.json b/translations/locales/zh-CN/translations.json index ce5e6cd027..d95a20f2e9 100644 --- a/translations/locales/zh-CN/translations.json +++ b/translations/locales/zh-CN/translations.json @@ -438,6 +438,7 @@ "AddSketch": "添加项目", "DeleteFromCollection": "您确定要从此集合中删除 {{name_sketch}} 吗?", "SketchDeleted": "项目已删除", + "SketchRemoveLabel": "Remove from Collection", "SketchRemoveARIA": "从集合中移除项目", "DescriptionPlaceholder": "添加描述", "Description": "描述", diff --git a/translations/locales/zh-TW/translations.json b/translations/locales/zh-TW/translations.json index ec501e52ad..d6138fcbb7 100644 --- a/translations/locales/zh-TW/translations.json +++ b/translations/locales/zh-TW/translations.json @@ -438,6 +438,7 @@ "AddSketch": "新增草稿", "DeleteFromCollection": "確定要從本作品集移除 {{name_sketch}}?", "SketchDeleted": "已刪除草稿", + "SketchRemoveLabel": "Remove from Collection", "SketchRemoveARIA": "從作品集移除草稿", "DescriptionPlaceholder": "新增說明", "Description": "說明",