Skip to content

Commit

Permalink
Open 200 - Tabbed Open dialog (#1195)
Browse files Browse the repository at this point in the history
  • Loading branch information
OlegMoshkovich authored Jun 19, 2024
1 parent c68605d commit 93dcb9a
Show file tree
Hide file tree
Showing 35 changed files with 528 additions and 155 deletions.
62 changes: 62 additions & 0 deletions cypress/e2e/open/100/open-model-dialog.cy.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import '@percy/cypress'
import {
auth0Login,
homepageSetup,
returningUserVisitsHomepageWaitForModel,
} from '../../../support/utils'
import {
setupVirtualPathIntercept,
waitForModelReady,
} from '../../../support/models'


/** {@link https://github.com/bldrs-ai/Share/issues/1159}*/
describe('Open 100: Open model dialog', () => {
beforeEach(homepageSetup)
context('First time user visits homepage not logged in', () => {
beforeEach(() => {
returningUserVisitsHomepageWaitForModel()
cy.get('[data-testid="control-button-open"]').click()
})

it('Sample tab to be selected and Momentum sample model chip to be visible', () => {
cy.get('[data-testid="tab-samples"]').click()
cy.get(':nth-child(1) > [data-testid="sample-model-chip"] > .MuiChip-label').contains('Momentum')
cy.percySnapshot()
})
it('Open button is visible', () => {
cy.get('[data-testid="tab-local"]').click()
cy.get('[data-testid="button_open_file"]').contains('Browse files...')
cy.percySnapshot()
})
})
context('Returning user visits homepage logged in', () => {
beforeEach(() => {
returningUserVisitsHomepageWaitForModel()
setupVirtualPathIntercept(
'/share/v/gh/cypresstester/test-repo/main/window.ifc',
'/index.ifc',
interceptTag)
auth0Login()
cy.get('[data-testid="control-button-open"]').click()
})
it('GitHub controls are visible', () => {
cy.get('[data-testid="tab-github"]').click()
cy.percySnapshot()
})
const interceptTag = 'ghOpenModelLoad'
it('Choose the path to the model on GitHub -> model is loaded into the scene', () => {
cy.get('[data-testid="tab-github"]').click()
cy.findByText('Github').click()
cy.findByLabelText('Organization', {timeout: 5000}).click()
cy.contains('@cypresstester').click()
cy.findByLabelText('Repository', {timeout: 5000}).eq(0).click()
cy.contains('test-repo').click()
cy.findByLabelText('File', {timeout: 5000}).eq(0).click()
cy.contains('window.ifc').click()
cy.get('[data-testid="button-openfromgithub"]').click()
waitForModelReady(interceptTag)
cy.percySnapshot()
})
})
})
5 changes: 3 additions & 2 deletions cypress/e2e/open/100/open-model-from-gh-ui.cy.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,14 @@ describe('Open 100: Open model from GH via UI', () => {
const interceptTag = 'ghOpenModelLoad'
it('Opens a model from Github via the UI - Screen', () => {
cy.get('[data-testid="control-button-open"]').click()
cy.findByLabelText('Organization').click()
cy.findByText('Github').click()
cy.findByLabelText('Organization', {timeout: 5000}).click()
cy.contains('@cypresstester').click()
cy.findByLabelText('Repository').eq(0).click()
cy.contains('test-repo').click()
cy.findByLabelText('File').eq(0).click()
cy.contains('window.ifc').click()
cy.contains('button', 'Load file').click()
cy.get('[data-testid="button-openfromgithub"]').click()
waitForModelReady(interceptTag)
cy.get('body').find('[data-testid="mui-dialog"]').should('not.exist')
cy.percySnapshot()
Expand Down
4 changes: 2 additions & 2 deletions cypress/e2e/open/100/open-sample-model.cy.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ describe('Open 100: Open Sample Model', () => {
context('Select OpenModelControl > Sample Models', () => {
beforeEach(() => {
cy.get('[data-testid="control-button-open"]').click()
cy.get('[data-testid="textfield-sample-projects"]').click()
cy.get('[data-testid="tab-samples"]').click()
})

it('Sample project list appears, including Momentum etc. - Screen', () => {
Expand Down Expand Up @@ -77,7 +77,7 @@ describe('Open 100: Open Sample Model', () => {
interceptModelLoadTag,
)
cy.get('[data-testid="control-button-open"]').click()
cy.get('[data-testid="textfield-sample-projects"]').click()
cy.get('[data-testid="tab-samples"]').click()
cy.findByText('Momentum').click()
waitForModelReady(interceptModelLoadTag)
})
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "bldrs",
"version": "1.0.1060",
"version": "1.0.1093",
"main": "src/index.jsx",
"license": "AGPL-3.0",
"homepage": "https://github.com/bldrs-ai/Share",
Expand Down
27 changes: 22 additions & 5 deletions src/Components/AlertDialog.jsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, {ReactElement} from 'react'
import React from 'react'
import Box from '@mui/material/Box'
import Dialog from '@mui/material/Dialog'
import DialogActions from '@mui/material/DialogActions'
Expand All @@ -17,15 +17,31 @@ import ErrorOutlineIcon from '@mui/icons-material/ErrorOutline'
import useTheme from '@mui/styles/useTheme'


/** @return {ReactElement} */
export default function AlertDialog({onClose, children}) {
/**
* Alert Dialog is presented when a model cannot be loaded
*
* @property {Function} onClose trigger close of the dialog
* @return {object} React component
*/
export default function AlertDialog({onClose}) {
const errorPath = useStore((state) => state.errorPath)
const setErrorPath = useStore((state) => state.setErrorPath)
const theme = useTheme()
const onCloseInner = () => {
setErrorPath(null)
onClose()
}

/**
* Insert the spaces after / _ character to make sure the string breaks correctly
*
* @property {string} str error path, usually a long string
* @return {string} formatted string
*/
const insertZeroWidthSpaces = (str) => {
return str.replace(/([/_-])/g, '$1\u200B')
}

return (
<Dialog
open={errorPath !== null}
Expand Down Expand Up @@ -92,9 +108,10 @@ export default function AlertDialog({onClose, children}) {
whiteSpace: 'normal',
}}
>
<Typography variant='body1' sx={{fontWeight: 'bold'}}>
Check the file path:
<br/>
{errorPath}
</Typography>
{errorPath && insertZeroWidthSpaces(errorPath)}
</Typography>
}
/>
Expand Down
2 changes: 1 addition & 1 deletion src/Components/AppBar.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ export default function AppBar({isRepoActive}) {
sx={{width: '100%'}}
>
<ControlsGroup isRepoActive={isRepoActive}/>
<SearchBar/>
<SearchBar placeholder='Search'/>
<LoginMenu/>
</Stack>
</Toolbar>
Expand Down
1 change: 0 additions & 1 deletion src/Components/Notes/NoteFooter.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@ export default function NoteFooter({
onClickShare,
selectCard,
selected,
setShowCreateComment,
showCreateComment,
submitUpdate,
synched,
Expand Down
164 changes: 164 additions & 0 deletions src/Components/Open/GitHubFileBrowser.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@

import React, {ReactElement, useState} from 'react'
import Button from '@mui/material/Button'
import Stack from '@mui/material/Stack'
import Typography from '@mui/material/Typography'
import {navigateBaseOnModelPath} from '../../utils/location'
import {useAuth0} from '../../Auth0/Auth0Proxy'
import {pathSuffixSupported} from '../../Filetype'
import {getFilesAndFolders} from '../../net/github/Files'
import {getRepositories, getUserRepositories} from '../../net/github/Repositories'
import useStore from '../../store/useStore'
import Selector from './Selector'
import SelectorSeparator from './SelectorSeparator'


/**
* @property {Function} navigate Callback from CadView to change page url
* @property {Array<string>} orgNamesArr List of org names for the current user.
* @property {Function} setIsDialogDisplayed callback
* @return {ReactElement}
*/
export default function GitHubFileBrowser({
navigate,
orgNamesArr,
setIsDialogDisplayed,
}) {
const [currentPath, setCurrentPath] = useState('')
const [foldersArr, setFoldersArr] = useState([''])
const {user} = useAuth0()
const [selectedOrgName, setSelectedOrgName] = useState('')
const [selectedRepoName, setSelectedRepoName] = useState('')
const [selectedFolderName, setSelectedFolderName] = useState('')
const [selectedFileIndex, setSelectedFileIndex] = useState('')
const [repoNamesArr, setRepoNamesArr] = useState([''])
const [filesArr, setFilesArr] = useState([''])
const accessToken = useStore((state) => state.accessToken)
const orgNamesArrWithAt = orgNamesArr.map((orgName) => `@${orgName}`)
const orgName = orgNamesArr[selectedOrgName]
const repoName = repoNamesArr[selectedRepoName]
const fileName = filesArr[selectedFileIndex]

const selectOrg = async (org) => {
setSelectedOrgName(org)
let repos
if (orgNamesArr[org] === user.nickname) {
repos = await getUserRepositories(accessToken)
} else {
repos = await getRepositories(orgNamesArr[org], accessToken)
}
const repoNames = Object.keys(repos).map((key) => repos[key].name)
setRepoNamesArr(repoNames)
setCurrentPath('')
setFoldersArr([''])
setSelectedFolderName('')
setSelectedFileIndex('')
setSelectedRepoName('')
}

const selectRepo = async (repo) => {
setSelectedRepoName(repo)
const owner = orgNamesArr[selectedOrgName]
const {files, directories} = await getFilesAndFolders(repoNamesArr[repo], owner, '/', accessToken)

const fileNames = files.map((file) => file.name)
const directoryNames = directories.map((directory) => directory.name)

setFilesArr(fileNames)
const foldersArrWithSeparator = [
...directoryNames,
]
setFoldersArr([...foldersArrWithSeparator])
setCurrentPath('')
setSelectedFolderName('')
setSelectedFileIndex('')
}

const selectFolder = async (folderIndex) => {
const owner = orgNamesArr[selectedOrgName]

// Get the selected folder name using the index
const selectedFolderName_ = foldersArr[folderIndex]

let newPath
if (selectedFolderName_ === '[Parent Directory]') {
// Move one directory up
const pathSegments = currentPath.split('/').filter(Boolean)
pathSegments.pop()
newPath = pathSegments.join('/')
} else {
// Navigate into a subfolder or stay at the root
newPath = selectedFolderName_ === '/' ? '' : `${currentPath}/${selectedFolderName_}`.replace('//', '/')
}

setSelectedFolderName('none')
setCurrentPath(newPath)

const {files, directories} = await getFilesAndFolders(repoName, owner, newPath, accessToken)
const fileNames = files.map((file) => file.name)
const directoryNames = directories.map((directory) => directory.name)

// Adjust navigation options based on the current level
const navigationOptions = newPath ? ['[Parent Directory]', ...directoryNames] : [...directoryNames]

setFilesArr(fileNames)
const foldersArrWithSeparator = [
...navigationOptions, // All the folders
]
setFoldersArr(foldersArrWithSeparator)
}

const navigateToFile = () => {
if (pathSuffixSupported(fileName)) {
// TODO(oleg): https://github.com/bldrs-ai/Share/issues/1215
navigate({pathname: navigateBaseOnModelPath(orgName, repoName, 'main', `${currentPath}/${fileName}`)})
setIsDialogDisplayed(false)
}
}
return (
<Stack data-testid={'stack_gitHub_access_controls'}>
<Stack>
<Typography variant='overline'>
Browse files on Github
</Typography>
<Selector
label='Organization'
list={orgNamesArrWithAt}
selected={selectedOrgName}
setSelected={selectOrg}
data-testid='openOrganization'
/>
<Selector
label='Repository'
list={repoNamesArr}
selected={selectedRepoName}
setSelected={selectRepo}
data-testid='openRepository'
/>
<SelectorSeparator
label={(currentPath === '') ? 'Folder' :
`Folder: ${currentPath}`}
list={foldersArr}
selected={selectedFolderName}
setSelected={selectFolder}
data-testid='saveFolder'
/>
<Selector
label='File'
list={filesArr}
selected={selectedFileIndex}
setSelected={setSelectedFileIndex}
data-testid='openFile'
/>
</Stack>
<Button
onClick={navigateToFile}
disabled={selectedFileIndex === ''}
variant='contained'
data-testid='button-openfromgithub'
>
Open from Github
</Button>
</Stack>
)
}
19 changes: 19 additions & 0 deletions src/Components/Open/GitHubFileBrowser.test.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import React from 'react'
import {render, screen} from '@testing-library/react'
import GitHubFileBrowser from './GitHubFileBrowser'


describe('GitHubFileBrowser', () => {
const orgNamesArr = ['org1', 'org2']
const user = {nickname: 'cypressTester'}
const navigate = jest.fn()

it('renders all the UI elements', () => {
render(<GitHubFileBrowser navigate={navigate} orgNamesArr={orgNamesArr} user={user}/>)
expect(screen.getByText(/Browse files on Github/i)).toBeInTheDocument()
expect(screen.getByLabelText(/Organization/i)).toBeInTheDocument()
expect(screen.getByLabelText(/Repository/i)).toBeInTheDocument()
expect(screen.getByLabelText(/Folder/i)).toBeInTheDocument()
expect(screen.getByLabelText(/File/i)).toBeInTheDocument()
})
})
4 changes: 2 additions & 2 deletions src/Components/Open/OpenModelControl.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import useStore from '../../store/useStore'
import {ControlButtonWithHashState} from '../Buttons'
import OpenModelDialog from './OpenModelDialog'
import {HASH_PREFIX_OPEN_MODEL} from './hashState'
import CreateNewFolderIcon from '@mui/icons-material/CreateNewFolderOutlined'
import FolderOpenIcon from '@mui/icons-material/FolderOpen'


/**
Expand Down Expand Up @@ -44,7 +44,7 @@ export default function OpenModelControl() {
return (
<ControlButtonWithHashState
title='Open'
icon={<CreateNewFolderIcon className='icon-share'/>}
icon={<FolderOpenIcon className='icon-share'/>}
isDialogDisplayed={isOpenModelVisible}
setIsDialogDisplayed={setIsOpenModelVisible}
hashPrefix={HASH_PREFIX_OPEN_MODEL}
Expand Down
Loading

0 comments on commit 93dcb9a

Please sign in to comment.