Skip to content

Commit

Permalink
Merge pull request #1664 from oodamien
Browse files Browse the repository at this point in the history
* pr/1664:
  Add ability to bookmark projects

Closes gh-1664
  • Loading branch information
mhalbritter committed Dec 6, 2024
2 parents 73349d8 + 84dfeff commit db5dc08
Show file tree
Hide file tree
Showing 22 changed files with 985 additions and 36 deletions.
5 changes: 4 additions & 1 deletion start-client/dev/api.mock.json
Original file line number Diff line number Diff line change
Expand Up @@ -1388,7 +1388,10 @@
"packaging": {
"type": "single-select",
"default": "jar",
"values": [{ "id": "jar", "name": "Jar" }, { "id": "war", "name": "War" }]
"values": [
{ "id": "jar", "name": "Jar" },
{ "id": "war", "name": "War" }
]
},
"javaVersion": {
"type": "single-select",
Expand Down
18 changes: 17 additions & 1 deletion start-client/src/components/Application.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ const Explore = lazy(() => import('./common/explore/Explore'))
const Share = lazy(() => import('./common/share/Share'))
const History = lazy(() => import('./common/history/History'))
const HotKeys = lazy(() => import('./common/builder/HotKeys'))
const Favorite = lazy(() => import('./common/favorite/Favorite'))

export default function Application() {
const {
Expand All @@ -34,6 +35,9 @@ export default function Application() {
share: shareOpen,
explore: exploreOpen,
history: historyOpen,
favorite: favoriteOpen,
favoriteAdd: favoriteAddOpen,
favoriteOptions,
list,
dependencies,
} = useContext(AppContext)
Expand Down Expand Up @@ -73,7 +77,9 @@ export default function Application() {
share: false,
explore: false,
nav: false,
history: false,
history: favoriteOptions.back === 'history',
favorite: favoriteOptions.back === 'favorite',
favoriteAdd: false,
},
})
}
Expand Down Expand Up @@ -120,6 +126,10 @@ export default function Application() {
dispatch({ type: 'UPDATE', payload: { share: true } })
}

const onFavoriteAdd = () => {
dispatch({ type: 'UPDATE', payload: { favoriteAdd: true } })
}

return (
<>
<BodyClassName className={theme} />
Expand Down Expand Up @@ -157,6 +167,7 @@ export default function Application() {
onSubmit={onSubmit}
onShare={onShare}
onExplore={onExplore}
onFavoriteAdd={onFavoriteAdd}
refExplore={buttonExplore}
refSubmit={buttonSubmit}
refDependency={buttonDependency}
Expand All @@ -177,6 +188,11 @@ export default function Application() {
onClose={onEscape}
/>
<History open={historyOpen || false} onClose={onEscape} />
<Favorite
add={favoriteAddOpen || false}
open={favoriteOpen || false}
onClose={onEscape}
/>
</Suspense>
</>
)
Expand Down
56 changes: 52 additions & 4 deletions start-client/src/components/common/builder/Fields.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import PropTypes from 'prop-types'
import get from 'lodash/get'
import React, { useContext } from 'react'
import React, { useContext, useRef, useState, useEffect } from 'react'

import Actions from './Actions'
import Control from './Control'
Expand All @@ -18,12 +18,15 @@ function Fields({
onSubmit,
onExplore,
onShare,
onFavoriteAdd,
refExplore,
refSubmit,
refDependency,
generating,
}) {
const wrapper = useRef(null)
const windowsUtils = useWindowsUtils()
const [dropdown, setDropdown] = useState(false)
const { config, dispatch, dependencies } = useContext(AppContext)
const {
values,
Expand All @@ -34,6 +37,19 @@ function Fields({
dispatchInitializr({ type: 'UPDATE', payload: args })
}

useEffect(() => {
const clickOutside = event => {
const children = get(wrapper, 'current')
if (children && !children.contains(event.target)) {
setDropdown(false)
}
}
document.addEventListener('mousedown', clickOutside)
return () => {
document.removeEventListener('mousedown', clickOutside)
}
}, [])

return (
<>
<div className='colset colset-main'>
Expand Down Expand Up @@ -183,9 +199,40 @@ function Fields({
>
Explore
</Button>
<Button id='share-project' onClick={onShare}>
Share...
</Button>

<span className='dropdown' ref={wrapper}>
<Button
className={`last-child ${dropdown ? 'clicked' : ''}`}
id='favorite-add'
onClick={() => {
setDropdown(true)
}}
>
...
</Button>
{dropdown && (
<div className='dropdown-items'>
<Button
id='favorite-add'
onClick={() => {
onFavoriteAdd()
setDropdown(false)
}}
>
Bookmark
</Button>
<Button
id='share-project'
onClick={() => {
onShare()
setDropdown(false)
}}
>
Share
</Button>
</div>
)}
</span>
</Actions>
</>
)
Expand All @@ -196,6 +243,7 @@ Fields.propTypes = {
onSubmit: PropTypes.func.isRequired,
onExplore: PropTypes.func.isRequired,
onShare: PropTypes.func.isRequired,
onFavoriteAdd: PropTypes.func.isRequired,
refExplore: PropTypes.oneOfType([
PropTypes.func,
PropTypes.shape({ current: PropTypes.instanceOf(Element) }),
Expand Down
5 changes: 3 additions & 2 deletions start-client/src/components/common/builder/Loading.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,9 @@ export default function Loading() {
<div className='colset'>
<div className='left'>
<Control text='Project'>
<Placeholder type='radio' width='97px' />
<Placeholder type='radio' width='98px' />
<Placeholder type='radio' width='113px' />
<Placeholder type='radio' width='100px' />
<Placeholder type='radio' width='47px' />
</Control>
</div>
<div className='right'>
Expand Down
185 changes: 185 additions & 0 deletions start-client/src/components/common/favorite/Add.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
import PropTypes from 'prop-types'
import get from 'lodash/get'
import React, { useEffect, useRef, useContext, useState } from 'react'
import { CSSTransition, TransitionGroup } from 'react-transition-group'
import { clearAllBodyScrollLocks, disableBodyScroll } from 'body-scroll-lock'
import queryString from 'query-string'
import { toast } from 'react-toastify'
import FieldInput from '../builder/FieldInput'
import { Button } from '../form'
import { AppContext } from '../../reducer/App'
import { InitializrContext } from '../../reducer/Initializr'
import {
getLabelFromList,
getLabelFromDepsList,
getBookmarkDefaultName,
} from './Utils'

function FavoriteItem({ value }) {
const { config } = useContext(AppContext)
const params = queryString.parse(value)
const deps = get(params, 'dependencies', '')
.split(',')
.filter(dep => !!dep)
return (
<div className='favorite-desc'>
<span className='desc'>
<span className='main'>
Project{' '}
<strong>
{getLabelFromList(
get(config, 'lists.project'),
get(params, 'type')
)}
</strong>
{`, `}
Language{' '}
<strong>
{getLabelFromList(
get(config, 'lists.language'),
get(params, 'language')
)}
</strong>
{`, `}
Spring Boot{' '}
<strong>
{getLabelFromList(
get(config, 'lists.boot'),
get(params, 'platformVersion')
)}
</strong>
</span>
<span className='deps'>
{deps.length === 0 && 'No dependency'}
{deps.length > 0 && (
<>
Dependencies:{' '}
<strong>
{deps
.map(dep =>
getLabelFromDepsList(get(config, 'lists.dependencies'), dep)
)
.join(', ')}
</strong>
</>
)}
</span>
</span>
</div>
)
}

FavoriteItem.propTypes = {
value: PropTypes.string.isRequired,
}

function Add({ open, onClose }) {
const wrapper = useRef(null)
const { share } = useContext(InitializrContext)
const { dispatch, favoriteOptions } = useContext(AppContext)
const [name, setName] = useState()
const input = useRef(null)

const title = get(favoriteOptions, 'title', '') || 'Bookmark'
const button = get(favoriteOptions, 'button', '') || 'Save'
const value = get(favoriteOptions, 'favorite.value', '') || share
const nameFav = get(favoriteOptions, 'favorite.name', '')

useEffect(() => {
setName(nameFav || `${getBookmarkDefaultName()}`)
}, [setName, open, nameFav])

useEffect(() => {
const clickOutside = event => {
const children = get(wrapper, 'current')
if (children && !children.contains(event.target)) {
onClose()
}
}
document.addEventListener('mousedown', clickOutside)
return () => {
document.removeEventListener('mousedown', clickOutside)
}
}, [onClose])

useEffect(() => {
if (get(wrapper, 'current') && open) {
disableBodyScroll(get(wrapper, 'current'))
}
if (get(input, 'current')) {
get(input, 'current').focus()
}
return () => {
clearAllBodyScrollLocks()
}
}, [wrapper, open])

const onSubmit = e => {
e.preventDefault()
if (!get(favoriteOptions, 'button', '')) {
dispatch({ type: 'ADD_FAVORITE', payload: { values: value, name } })
toast.success('Project bookmarked')
} else {
dispatch({
type: 'UPDATE_FAVORITE',
payload: { name, favorite: favoriteOptions.favorite },
})
}
onClose()
}

return (
<TransitionGroup component={null}>
{open && (
<CSSTransition classNames='popup' timeout={300}>
<div className='modal-add-favorite'>
<form className='form' onSubmit={onSubmit} autoComplete='off'>
<div className='modal-add-favorite-container' ref={wrapper}>
<div className='modal-header'>
<h1>{title}</h1>
</div>
<div className='modal-content'>
<FavoriteItem value={value} />
<FieldInput
id='input-name'
value={name}
text='Name'
inputRef={input}
onChange={event => {
setName(`${event.target.value}`)
}}
/>
</div>
<div className='modal-action'>
<Button
id='add-favorite'
variant='primary'
onClick={onSubmit}
>
{button}
</Button>
<Button
hotkey='ESC'
onClick={e => {
e.preventDefault()
onClose()
}}
>
Close
</Button>
</div>
</div>
</form>
</div>
</CSSTransition>
)}
</TransitionGroup>
)
}

Add.propTypes = {
open: PropTypes.bool.isRequired,
onClose: PropTypes.func.isRequired,
}

export default Add
26 changes: 26 additions & 0 deletions start-client/src/components/common/favorite/Favorite.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import '../../../styles/favorite.scss'

import PropTypes from 'prop-types'
import React from 'react'

import Modal from './Modal'
import Add from './Add'
import { Overlay } from '../form'

function Favorite({ open, add, onClose }) {
return (
<>
<Modal open={open || false} onClose={onClose} />
<Add open={add || false} onClose={onClose} />
<Overlay open={open || add} />
</>
)
}

Favorite.propTypes = {
open: PropTypes.bool.isRequired,
add: PropTypes.bool.isRequired,
onClose: PropTypes.func.isRequired,
}

export default Favorite
Loading

0 comments on commit db5dc08

Please sign in to comment.