Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Extend search bar functionality to open IFCs hosted on GitHub. #174

Merged
merged 23 commits into from
Apr 22, 2022
Merged
Show file tree
Hide file tree
Changes from 16 commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
e1b9eb7
add a method to check if the input is URL
OlegMoshkovich Apr 6, 2022
67a1f70
rename
OlegMoshkovich Apr 6, 2022
1dba14d
extract url parameters from search input
OlegMoshkovich Apr 7, 2022
ec38c33
debug the model path
OlegMoshkovich Apr 9, 2022
0587663
add styles and funcitons to the search bar
OlegMoshkovich Apr 12, 2022
2ede2ba
moved url methods to share routes
OlegMoshkovich Apr 12, 2022
ad3da39
new build
OlegMoshkovich Apr 12, 2022
b88403e
new build
OlegMoshkovich Apr 13, 2022
d885dc5
add test to share routes
OlegMoshkovich Apr 13, 2022
d605e15
add test to share routes
OlegMoshkovich Apr 13, 2022
ef1a3f0
add test to share routes
OlegMoshkovich Apr 13, 2022
b4265d2
fixes
OlegMoshkovich Apr 13, 2022
87c9365
add tests + parse url method
OlegMoshkovich Apr 15, 2022
d7e5cf4
add isURL and error messaging
OlegMoshkovich Apr 15, 2022
e54ad70
clean up
OlegMoshkovich Apr 15, 2022
bafbec4
clean up
OlegMoshkovich Apr 15, 2022
3f46328
add fixes
OlegMoshkovich Apr 15, 2022
806ff13
fixes
OlegMoshkovich Apr 15, 2022
d6bbb72
search: thoughts on how to use regex capture group for github url par…
pablo-mayrgundter Apr 18, 2022
f85f25a
search: support both full URLs and paths in search.
pablo-mayrgundter Apr 19, 2022
855107c
search: More case handling for load-by-link in searchbar.
pablo-mayrgundter Apr 21, 2022
d02c377
search: little more refactoring and test cases.
pablo-mayrgundter Apr 22, 2022
304a17e
Merge remote-tracking branch 'upstream/main' into search_URL
pablo-mayrgundter Apr 22, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ module.exports = {
'jsx-a11y',
],
rules: {
'max-len': ['error', 100],
'max-len': ['error', 140],
'no-irregular-whitespace': ['error'],
'no-trailing-spaces': ['error'],
'prefer-rest-params': 'off',
Expand Down
391 changes: 196 additions & 195 deletions docs/index.js

Large diffs are not rendered by default.

6 changes: 3 additions & 3 deletions docs/index.js.map

Large diffs are not rendered by default.

7 changes: 3 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "buildrs",
"version": "1.0.0-r270",
"name": "bldrs-ai",
OlegMoshkovich marked this conversation as resolved.
Show resolved Hide resolved
"version": "1.0.0-r283",
"main": "src/index.jsx",
"homepage": "https://github.com/bldrs-ai/Share",
"bugs": {
Expand Down Expand Up @@ -61,8 +61,7 @@
"verbose": false,
"testEnvironment": "jsdom",
"testPathIgnorePatterns": [
"src/Share.test.js",
"src/ShareRoutes.test.js"
"src/Share.test.js"
],
"transform": {
"\\.[jt]sx?$": "babel-jest",
Expand Down
1 change: 0 additions & 1 deletion src/Components/AboutControl.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,6 @@ function AboutContent({installPrefix}) {
</ul>
<Typography variant='h5' color='info'>Highlighted Projects:</Typography>
<div className = {classes.demoContainer}>
{/* eslint-disable-next-line */}
OlegMoshkovich marked this conversation as resolved.
Show resolved Hide resolved
<a href={`${installPrefix}/share/v/gh/Swiss-Property-AG/Portfolio/main/KNIK.ifc#c:-12.84,3.53,9.64,-5.33,2.61,1.71`}>
<img alt="Tinyhouse" src={`${installPrefix}/Tinyhouse.png`} className={classes.demo}/>
</a>
Expand Down
12 changes: 7 additions & 5 deletions src/Components/Buttons.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -46,11 +46,12 @@ export function TooltipToggleButton({
onClick,
title,
icon,
size = 'medium',
placement='left',
}) {
assertDefined(title, icon, onClick)
const [isPressed, setIsPressed] = useState(false)
const classes = useStyles(useTheme())
const classes = useStyles(size === 'small' ? {buttonWidth: '40px'} : {buttonWidth: '50px'})
return (
<div className={classes.root}>
<Tooltip title={title} describeChild placement={placement}>
Expand Down Expand Up @@ -89,11 +90,12 @@ export function ControlButton({
setIsDialogDisplayed,
icon,
placement='left',
size = 'medium',
dialog,
}) {
assertDefined(title, isDialogDisplayed, setIsDialogDisplayed, icon, dialog)
const toggleIsDialogDisplayed = () => setIsDialogDisplayed(!isDialogDisplayed)
const classes = useStyles(useTheme())
const classes = useStyles(size === 'small' ? {buttonWidth: '40px'} : {buttonWidth: '50px'})
OlegMoshkovich marked this conversation as resolved.
Show resolved Hide resolved
return (
<div className={classes.root}>
<Tooltip title={title} describeChild placement={placement}>
Expand All @@ -120,7 +122,7 @@ export function ControlButton({
*/
export function FormButton({title, icon, type='submit', placement='left', size='medium'}) {
assertDefined(title, icon)
const classes = useStyles(useTheme())
const classes = useStyles(size === 'small' ? {buttonWidth: '40px'} : {buttonWidth: '50px'})
OlegMoshkovich marked this conversation as resolved.
Show resolved Hide resolved
return (
<div className={classes.root}>
<Tooltip title={title} describeChild placement={placement}>
Expand All @@ -136,8 +138,8 @@ export function FormButton({title, icon, type='submit', placement='left', size='
const useStyles = makeStyles((theme) => ({
root: {
'& button': {
width: '50px',
height: '50px',
width: (props) => props.buttonWidth || '50px',
height: (props) => props.buttonWidth || '50px',
border: 'none',
borderRadius: '50%',
},
Expand Down
1 change: 0 additions & 1 deletion src/Components/ItemProperties.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,6 @@ async function createPsetsList(model, element, classes, expandAll) {
}


/* eslint-disable max-len*/
/**
* The keys are defined here:
* https://standards.buildingsmart.org/IFC/DEV/IFC4_3/RC2/HTML/schema/ifcproductextension/lexical/ifcelement.htm
Expand Down
2 changes: 1 addition & 1 deletion src/Components/NavPanel.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ export default function NavPanel({
const useStyles = makeStyles({
root: {
'position': 'absolute',
'top': '80px',
'top': '94px',
'left': '20px',
'overflow': 'auto',
'width': '300px',
Expand Down
89 changes: 72 additions & 17 deletions src/Components/SearchBar.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,10 @@ import Paper from '@mui/material/Paper'
import {makeStyles} from '@mui/styles'
import {TooltipToggleButton, FormButton} from './Buttons'
import debug from '../utils/debug'
import {isURL, isValidModelURL, parseIfcURL} from '../ShareRoutes'
import SearchIcon from '../assets/2D_Icons/Search.svg'
import LinkIcon from '../assets/2D_Icons/Link.svg'
import ClearIcon from '../assets/2D_Icons/Close.svg'
import TreeIcon from '../assets/2D_Icons/Tree.svg'


Expand All @@ -24,10 +27,10 @@ export default function SearchBar({onClickMenuCb, showNavPanel}) {
const navigate = useNavigate()
const [searchParams, setSearchParams] = useSearchParams()
const [inputText, setInputText] = useState('')
const [error, setError] = useState('')
const onInputChange = (event) => setInputText(event.target.value)
const searchInputRef = useRef(null)
const classes = useStyles()

const classes = useStyles({inputTextLength: Number(inputText.length) * 11})
OlegMoshkovich marked this conversation as resolved.
Show resolved Hide resolved

useEffect(() => {
debug().log('SearchBar#useEffect[searchParams]')
Expand All @@ -44,10 +47,20 @@ export default function SearchBar({onClickMenuCb, showNavPanel}) {
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [searchParams])


const onSubmit = (event) => {
// Prevent form event bubbling and causing page reload.
event.preventDefault()
setError('')
OlegMoshkovich marked this conversation as resolved.
Show resolved Hide resolved
// if url is typed into the search bar open the model
if (isURL(inputText)) {
pablo-mayrgundter marked this conversation as resolved.
Show resolved Hide resolved
if (isValidModelURL(inputText)) {
const modelPath = parseIfcURL(inputText)
navigate(modelPath, {replace: true})
return
} else {
setError(`Please enter a valid url. Click on the LINK icon to learn more.`)
}
}

// Searches from SearchBar clear current URL's IFC path.
if (containsIfcPath(location)) {
Expand All @@ -63,18 +76,50 @@ export default function SearchBar({onClickMenuCb, showNavPanel}) {
}

return (
<Paper component='form' className={classes.root} onSubmit={onSubmit}>
<TooltipToggleButton
title='Toggle tree view'
onClick={onClickMenuCb}
icon={<TreeIcon/>}/>
<InputBase
inputRef={searchInputRef}
value={inputText}
onChange={onInputChange}
placeholder={'Search model'}/>
<FormButton title='search' icon={<SearchIcon/>}/>
</Paper>
<div>
<Paper component='form' className={classes.root} onSubmit={onSubmit}>
<TooltipToggleButton
placement = 'bottom'
title='Toggle tree view'
onClick={onClickMenuCb}
icon={<TreeIcon/>}/>
<InputBase
inputRef={searchInputRef}
value={inputText}
onChange={onInputChange}
error = {true}
placeholder={'Search model'}/>
{inputText.length>0?
<TooltipToggleButton
title='clear'
size = 'small'
placement = 'bottom'
onClick={()=>{
setInputText('')
setError('')
}}
icon={<ClearIcon/>}/>:null
}
<FormButton
title='search'
size = 'small'
placement = 'bottom'
icon={<SearchIcon/>}/>
<TooltipToggleButton
title={`Type GitHUB URL to access IFCs hosted on GitHUB.
OlegMoshkovich marked this conversation as resolved.
Show resolved Hide resolved
Click on the link icon to learn more.`}
size = 'small'
placement = 'right'
onClick={()=>{
window.open('https://github.com/bldrs-ai/Share/wiki/Open-IFC-model-hosted-on-GitHub')
}}
icon={<LinkIcon/>}/>
</Paper>
{ inputText.length>0 &&
error.length>0 &&
<div className = {classes.error}>{error}</div>
}
</div>
)
}

Expand Down Expand Up @@ -142,14 +187,24 @@ export function stripIfcPathFromLocation(location, fileExtension = '.ifc') {
const useStyles = makeStyles({
root: {
'display': 'flex',
'width': '300px',
'minWidth': '300px',
'width': (props) => props.inputTextLength,
'maxWidth': '800px',
'alignItems': 'center',
'padding': '2px 2px 2px 2px',
'@media (max-width: 900px)': {
width: '250px',
minWidth: '300px',
width: '300px',
maxWidth: '300px',
},
'& .MuiInputBase-root': {
flex: 1,
},
},
error: {
marginLeft: '10px',
marginTop: '3px',
fontSize: '10px',
color: 'red',
},
})
7 changes: 4 additions & 3 deletions src/Components/SnackbarMessage.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ export default function SnackBarMessage({message, type, open}) {
severity={type}
className = {classes.root}
sx = {{backgroundColor: '#848484'}}
icon={false}
>
{message}
</Alert>
Expand All @@ -35,13 +36,13 @@ const Alert = React.forwardRef(function Alert(props, ref) {
const useStyles = makeStyles({
root: {
'position': 'relative',
'bottom': '60px',
'bottom': '70px',
'left': '6px',
'@media (max-width: 900px)': {
left: '18px',
bottom: '90px',
width: '220px',
inlineSize: '220px',
width: '305px',
inlineSize: '305px',
overflow: 'visible',
overflowWrap: 'anywhere',
},
Expand Down
2 changes: 0 additions & 2 deletions src/Containers/CadView.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,6 @@ export default function CadView({
}) {
assertDefined(...arguments)
debug().log('CadView#init: count: ', count++)

// React router
const navigate = useNavigate()
// TODO(pablo): Removing this setter leads to a very strange stack overflow
Expand Down Expand Up @@ -465,7 +464,6 @@ const useStyles = makeStyles({
// to hardcode for now and look into passing via the theme later.
top: `20px`,
left: '20px',
width: '300px',
display: 'flex',
flexDirection: 'row',
justifyContent: 'flex-start',
Expand Down
74 changes: 74 additions & 0 deletions src/ShareRoutes.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
useLocation,
useNavigate,
} from 'react-router-dom'
import {assertDefined} from './utils/assert'
import Share from './Share'
import debug from './utils/debug'

Expand All @@ -32,6 +33,79 @@ function Forward({appPrefix}) {
}


/**
* Check if input is a url
* @param {input} input
* @return {boolean} return true if url is found
*/
export function isURL(input) {
assertDefined(input)
return input.includes('https://') || input.includes('www')
}


/**
* Check if input is a valid url
* @param {Object} input
* @return {boolean} return true if url is found
*/
export function isValidModelURL(input) {
OlegMoshkovich marked this conversation as resolved.
Show resolved Hide resolved
assertDefined(input)
try {
const url = new URL(input)
const parsed = new URL(url)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is redundant

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably.
This is the method Ogali suggested, and I was under the impression you approved of it.

if (parsed.hostname !== 'github.com' &&
parsed.hostname !== 'www.github.com' &&
parsed.hostname !== 'raw.githubusercontent.com') {
return false
}
return true
OlegMoshkovich marked this conversation as resolved.
Show resolved Hide resolved
} catch (e) {
return false
}
}


/**
* ParseIfcURL parses Github repository URLs or raw content URLs
* @param {string} url
* @return {string} Structured path to the model repositore
OlegMoshkovich marked this conversation as resolved.
Show resolved Hide resolved
*/
export const parseIfcURL = (url) => {
OlegMoshkovich marked this conversation as resolved.
Show resolved Hide resolved
assertDefined(url)
const parsed = new URL(url)
const parts = parsed.pathname.split('/')
OlegMoshkovich marked this conversation as resolved.
Show resolved Hide resolved
switch (parsed.hostname) {
case 'github.com':
case 'www.github.com':
if (parts.length > 4 || parts[3] === 'blob') {
const URLParameters = {
org: parts[1],
repo: parts[2],
branch: parts[4],
path: parts[5],
}
const modelPath = `/share/v/gh/${URLParameters.org}/${URLParameters.repo}/${URLParameters.branch}/${URLParameters.path}`
return modelPath
}
throw new Error('unsupported Github repository URL')
case 'raw.githubusercontent.com':
if (parts.length > 3) {
const URLParameters = {
org: parts[1],
repo: parts[2],
branch: parts[3],
path: parts[4],
}
const modelPath = `/share/v/gh/${URLParameters.org}/${URLParameters.repo}/${URLParameters.branch}/${URLParameters.path}`
return modelPath
}
throw new Error('unsupported Github user content URL')
default:
throw new Error('unexpected case!')
}
}

/**
* For URL design see: https://github.com/bldrs-ai/Share/wiki/URL-Structure
*
Expand Down