From ca8296a8ff4242c7a1c66673a6f74b4ba51d5d1d Mon Sep 17 00:00:00 2001 From: dewanshDT Date: Fri, 4 Aug 2023 19:06:36 +0530 Subject: [PATCH 1/2] context and the new nav --- client/modules/IDE/components/Header/Nav.jsx | 318 ++++++++++++++++++ .../modules/IDE/components/Header/index.jsx | 34 ++ client/modules/IDE/hooks/index.js | 77 +++++ client/modules/IDE/pages/IDEView.jsx | 8 +- package-lock.json | 2 +- package.json | 4 +- 6 files changed, 438 insertions(+), 5 deletions(-) create mode 100644 client/modules/IDE/components/Header/Nav.jsx create mode 100644 client/modules/IDE/components/Header/index.jsx create mode 100644 client/modules/IDE/hooks/index.js diff --git a/client/modules/IDE/components/Header/Nav.jsx b/client/modules/IDE/components/Header/Nav.jsx new file mode 100644 index 0000000000..551fc36717 --- /dev/null +++ b/client/modules/IDE/components/Header/Nav.jsx @@ -0,0 +1,318 @@ +import React, { useContext } from 'react'; +import { useDispatch, useSelector } from 'react-redux'; +import { sortBy } from 'lodash'; +import { Link } from 'react-router-dom'; +import PropTypes from 'prop-types'; + +import { useTranslation } from 'react-i18next'; +import NavDropdownMenu from '../../../../components/Nav/NavDropdownMenu'; +import NavMenuItem from '../../../../components/Nav/NavMenuItem'; +import { availableLanguages, languageKeyToLabel } from '../../../../i18n'; +import getConfig from '../../../../utils/getConfig'; +import { showToast } from '../../actions/toast'; +import { setLanguage } from '../../actions/preferences'; +import NavBar from '../../../../components/Nav/NavBar'; +import CaretLeftIcon from '../../../../images/left-arrow.svg'; +import LogoIcon from '../../../../images/p5js-logo-small.svg'; +import { selectSketchPath } from '../../selectors/project'; +import { metaKey, metaKeyName } from '../../../../utils/metaKey'; +import { useSketchActions } from '../../hooks'; +import { getIsUserOwner } from '../../selectors/users'; +import { cloneProject } from '../../actions/project'; +import { + newFile, + newFolder, + showKeyboardShortcutModal, + startSketch, + stopSketch +} from '../../actions/ide'; +import { logoutUser } from '../../../User/actions'; +import { CmControllerContext } from '../../pages/IDEView'; + +const Nav = ({ layout }) => ( + + + + +); + +Nav.propTypes = { + layout: PropTypes.oneOf(['dashboard', 'project']) +}; + +Nav.defaultProps = { + layout: 'project' +}; + +const LeftLayout = (props) => { + switch (props.layout) { + case 'dashboard': + return ; + case 'project': + default: + return ; + } +}; + +LeftLayout.propTypes = { + layout: PropTypes.oneOf(['dashboard', 'project']) +}; + +LeftLayout.defaultProps = { + layout: 'project' +}; + +const UserMenu = () => { + const user = useSelector((state) => state.user); + + const isLoginEnabled = getConfig('LOGIN_ENABLED'); + const isAuthenticated = user.authenticated; + + if (isLoginEnabled && isAuthenticated) { + return ; + } else if (isLoginEnabled && !isAuthenticated) { + return ; + } + + return null; +}; + +const DashboardMenu = () => { + const { t } = useTranslation(); + const editorLink = useSelector(selectSketchPath); + return ( +
    +
  • + +
  • +
  • + +
  • +
+ ); +}; + +const ProjectMenu = (props) => { + const isUserOwner = useSelector(getIsUserOwner); + const project = useSelector((state) => state?.project); + const user = useSelector((state) => state.user); + + // TODO: use selectRootFile selector + const rootFile = useSelector( + (state) => state.files.filter((file) => file.name === 'root')[0] + ); + + const cmRef = useContext(CmControllerContext); + + const dispatch = useDispatch(); + + const { t } = useTranslation(); + const { + newSketch, + saveSketch, + downloadSketch, + shareSketch + } = useSketchActions(); + + const replaceCommand = + metaKey === 'Ctrl' ? `${metaKeyName}+H` : `${metaKeyName}+⌥+F`; + + return ( +
    +
  • + +
  • + + {t('Nav.File.New')} + saveSketch(cmRef)} + > + {t('Common.Save')} + {metaKeyName}+S + + dispatch(cloneProject())} + > + {t('Nav.File.Duplicate')} + + + {t('Nav.File.Share')} + + + {t('Nav.File.Download')} + + + {t('Nav.File.Open')} + + + {t('Nav.File.AddToCollection')} + + + {t('Nav.File.Examples')} + + + + + {t('Nav.Edit.TidyCode')} + + {metaKeyName}+{'\u21E7'}+F + + + + {t('Nav.Edit.Find')} + {metaKeyName}+F + + + {t('Nav.Edit.Replace')} + {replaceCommand} + + + + dispatch(newFile(rootFile.id))}> + {t('Nav.Sketch.AddFile')} + + dispatch(newFolder(rootFile.id))}> + {t('Nav.Sketch.AddFolder')} + + dispatch(startSketch())}> + {t('Nav.Sketch.Run')} + {metaKeyName}+Enter + + dispatch(stopSketch())}> + {t('Nav.Sketch.Stop')} + + {'\u21E7'}+{metaKeyName}+Enter + + + + + dispatch(showKeyboardShortcutModal())}> + {t('Nav.Help.KeyboardShortcuts')} + + + {t('Nav.Help.Reference')} + + {t('Nav.Help.About')} + +
+ ); +}; + +const LanguageMenu = () => { + const language = useSelector((state) => state.preferences.language); + const dispatch = useDispatch(); + + function handleLangSelection(event) { + dispatch(setLanguage(event.target.value)); + dispatch(showToast('Toast.LangChange')); + } + + return ( + + {sortBy(availableLanguages).map((key) => ( + // eslint-disable-next-line react/jsx-no-bind + + {languageKeyToLabel(key)} + + ))} + + ); +}; + +const UnauthenticatedUserMenu = () => { + const { t } = useTranslation(); + return ( +
    + {getConfig('TRANSLATIONS_ENABLED') && } +
  • + + + {t('Nav.Login')} + + +
  • + {t('Nav.LoginOr')} +
  • + + + {t('Nav.SignUp')} + + +
  • +
+ ); +}; + +const AuthenticatedUserMenu = () => { + const user = useSelector((state) => state.user); + + const { t } = useTranslation(); + const dispatch = useDispatch(); + + return ( +
    + {getConfig('TRANSLATIONS_ENABLED') && } + + {t('Nav.Auth.Hello')}, {user.username}! + + } + > + + {t('Nav.Auth.MySketches')} + + + {t('Nav.Auth.MyCollections')} + + + {t('Nav.Auth.MyAssets')} + + {t('Preferences.Settings')} + dispatch(logoutUser())}> + {t('Nav.Auth.LogOut')} + + +
+ ); +}; + +export default Nav; diff --git a/client/modules/IDE/components/Header/index.jsx b/client/modules/IDE/components/Header/index.jsx new file mode 100644 index 0000000000..366095c340 --- /dev/null +++ b/client/modules/IDE/components/Header/index.jsx @@ -0,0 +1,34 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { useSelector } from 'react-redux'; +import MediaQuery from 'react-responsive'; +import Toolbar from '../Toolbar'; +import Nav from './Nav'; + +const Header = (props) => { + const project = useSelector((state) => state.project); + + return ( +
+
+ ); +}; + +Header.propTypes = { + syncFileContent: PropTypes.func.isRequired +}; + +export default Header; diff --git a/client/modules/IDE/hooks/index.js b/client/modules/IDE/hooks/index.js new file mode 100644 index 0000000000..343e9e6adc --- /dev/null +++ b/client/modules/IDE/hooks/index.js @@ -0,0 +1,77 @@ +import { useDispatch, useSelector } from 'react-redux'; +import { useTranslation } from 'react-i18next'; +import { useParams } from 'react-router'; +import { + autosaveProject, + exportProjectAsZip, + newProject, + saveProject, + setProjectName +} from '../actions/project'; +import { showToast } from '../actions/toast'; +import { showErrorModal, showShareModal } from '../actions/ide'; + +export const useSketchActions = () => { + const unsavedChanges = useSelector((state) => state.ide.unsavedChanges); + const authenticated = useSelector((state) => state.user.authenticated); + const project = useSelector((state) => state.project); + const currentUser = useSelector((state) => state.user.username); + const dispatch = useDispatch(); + const { t } = useTranslation(); + const params = useParams(); + + function newSketch() { + if (!unsavedChanges) { + dispatch(showToast('Toast.OpenedNewSketch')); + dispatch(newProject()); + } else if (window.confirm(t('Nav.WarningUnsavedChanges'))) { + dispatch(showToast('Toast.OpenedNewSketch')); + dispatch(newProject()); + } + } + + function saveSketch(cmController) { + if (authenticated) { + dispatch(saveProject(cmController?.getContent())); + } else { + dispatch(showErrorModal('forceAuthentication')); + } + } + + function downloadSketch() { + dispatch(autosaveProject()); + dispatch(exportProjectAsZip(project.id)); + } + + function shareSketch() { + const { username } = params; + dispatch(showShareModal(project.id, project.name, username)); + } + + function changeSketchName(name) { + const newProjectName = name.trim(); + if (newProjectName.length > 0) { + dispatch(setProjectName(newProjectName)); + if (project.id) dispatch(saveProject()); + } + } + + function canEditProjectName() { + return ( + (project.owner && + project.owner.username && + project.owner.username === currentUser) || + !project.owner || + !project.owner.username + ); + } + + return { + newSketch, + saveSketch, + downloadSketch, + shareSketch, + changeSketchName, + canEditProjectName + }; +}; diff --git a/client/modules/IDE/pages/IDEView.jsx b/client/modules/IDE/pages/IDEView.jsx index 040b682605..16a3919041 100644 --- a/client/modules/IDE/pages/IDEView.jsx +++ b/client/modules/IDE/pages/IDEView.jsx @@ -17,7 +17,6 @@ import UploadFileModal from '../components/UploadFileModal'; import ShareModal from '../components/ShareModal'; import KeyboardShortcutModal from '../components/KeyboardShortcutModal'; import ErrorModal from '../components/ErrorModal'; -import Nav from '../../../components/Nav'; import Console from '../components/Console'; import Toast from '../components/Toast'; import * as FileActions from '../actions/files'; @@ -35,6 +34,7 @@ import Feedback from '../components/Feedback'; import { CollectionSearchbar } from '../components/Searchbar'; import { getIsUserOwner } from '../selectors/users'; import RootPage from '../../../components/RootPage'; +import Nav from '../components/Header/Nav'; function getTitle(props) { const { id } = props.project; @@ -74,6 +74,8 @@ function WarnIfUnsavedChanges() { ); } +export const CmControllerContext = React.createContext({}); + class IDEView extends React.Component { constructor(props) { super(props); @@ -257,7 +259,9 @@ class IDEView extends React.Component { -