Skip to content
GitLab
Projects
Groups
Snippets
Help
Loading...
Help
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
S
scratch-www
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Analytics
Analytics
Repository
Value Stream
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Commits
Open sidebar
xpstem
scratch-www
Commits
35dbcb07
Commit
35dbcb07
authored
May 10, 2021
by
Paul Kaplan
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Add user projects modal
parent
f1fde9e5
Changes
9
Show whitespace changes
Inline
Side-by-side
Showing
9 changed files
with
420 additions
and
64 deletions
+420
-64
src/views/studio/lib/redux-modules.js
src/views/studio/lib/redux-modules.js
+3
-1
src/views/studio/lib/user-projects-actions.js
src/views/studio/lib/user-projects-actions.js
+54
-0
src/views/studio/modals/user-projects-modal.jsx
src/views/studio/modals/user-projects-modal.jsx
+118
-0
src/views/studio/modals/user-projects-modal.scss
src/views/studio/modals/user-projects-modal.scss
+92
-0
src/views/studio/modals/user-projects-tile.jsx
src/views/studio/modals/user-projects-tile.jsx
+57
-0
src/views/studio/studio-curator-inviter.jsx
src/views/studio/studio-curator-inviter.jsx
+25
-22
src/views/studio/studio-project-adder.jsx
src/views/studio/studio-project-adder.jsx
+36
-23
src/views/studio/studio.jsx
src/views/studio/studio.jsx
+3
-1
src/views/studio/studio.scss
src/views/studio/studio.scss
+32
-17
No files found.
src/views/studio/lib/redux-modules.js
View file @
35dbcb07
...
...
@@ -5,6 +5,8 @@ const curators = InfiniteList('curators');
const
managers
=
InfiniteList
(
'
managers
'
);
const
activity
=
InfiniteList
(
'
activity
'
);
const
userProjects
=
InfiniteList
(
'
user-projects
'
);
export
{
projects
,
curators
,
managers
,
activity
projects
,
curators
,
managers
,
activity
,
userProjects
};
src/views/studio/lib/user-projects-actions.js
0 → 100644
View file @
35dbcb07
import
keyMirror
from
'
keymirror
'
;
import
api
from
'
../../../lib/api
'
;
import
{
selectUsername
}
from
'
../../../redux/session
'
;
import
{
userProjects
}
from
'
./redux-modules
'
;
const
Errors
=
keyMirror
({
NETWORK
:
null
,
SERVER
:
null
,
PERMISSION
:
null
});
const
Filters
=
keyMirror
({
SHARED
:
null
,
FAVORITED
:
null
,
RECENT
:
null
});
const
Uris
=
{
[
Filters
.
SHARED
]:
username
=>
`/users/
${
username
}
/projects`
,
[
Filters
.
FAVORITED
]:
username
=>
`/users/
${
username
}
/favorites`
,
[
Filters
.
RECENT
]:
username
=>
`/users/
${
username
}
/recent`
};
const
normalizeError
=
(
err
,
body
,
res
)
=>
{
if
(
err
)
return
Errors
.
NETWORK
;
if
(
res
.
statusCode
===
401
||
res
.
statusCode
===
403
)
return
Errors
.
PERMISSION
;
if
(
res
.
statusCode
!==
200
)
return
Errors
.
SERVER
;
return
null
;
};
const
loadUserProjects
=
type
=>
((
dispatch
,
getState
)
=>
{
const
state
=
getState
();
const
username
=
selectUsername
(
state
);
const
projectCount
=
userProjects
.
selector
(
state
).
items
.
length
;
const
projectsPerPage
=
20
;
dispatch
(
userProjects
.
actions
.
loading
());
api
({
uri
:
Uris
[
type
](
username
),
params
:
{
limit
:
projectsPerPage
,
offset
:
projectCount
}
},
(
err
,
body
,
res
)
=>
{
const
error
=
normalizeError
(
err
,
body
,
res
);
if
(
error
)
return
dispatch
(
userProjects
.
actions
.
error
(
error
));
dispatch
(
userProjects
.
actions
.
append
(
body
,
body
.
length
===
projectsPerPage
));
});
});
// Re-export clear so that the consumer can manage filter changes
const
clearUserProjects
=
userProjects
.
actions
.
clear
;
export
{
Filters
,
loadUserProjects
,
clearUserProjects
};
src/views/studio/modals/user-projects-modal.jsx
0 → 100644
View file @
35dbcb07
/* eslint-disable react/jsx-no-bind */
import
React
,
{
useEffect
,
useState
}
from
'
react
'
;
import
PropTypes
from
'
prop-types
'
;
import
{
connect
}
from
'
react-redux
'
;
import
classNames
from
'
classnames
'
;
import
{
addProject
,
removeProject
}
from
'
../lib/studio-project-actions
'
;
import
{
userProjects
}
from
'
../lib/redux-modules
'
;
import
{
Filters
,
loadUserProjects
,
clearUserProjects
}
from
'
../lib/user-projects-actions
'
;
import
Modal
from
'
../../../components/modal/base/modal.jsx
'
;
import
ModalTitle
from
'
../../../components/modal/base/modal-title.jsx
'
;
import
ModalInnerContent
from
'
../../../components/modal/base/modal-inner-content.jsx
'
;
import
SubNavigation
from
'
../../../components/subnavigation/subnavigation.jsx
'
;
import
UserProjectsTile
from
'
./user-projects-tile.jsx
'
;
import
'
./user-projects-modal.scss
'
;
const
UserProjectsModal
=
({
items
,
error
,
loading
,
moreToLoad
,
onLoadMore
,
onClear
,
onAdd
,
onRemove
,
onRequestClose
})
=>
{
const
[
filter
,
setFilter
]
=
useState
(
Filters
.
SHARED
);
useEffect
(()
=>
{
onClear
();
onLoadMore
(
filter
);
},
[
filter
]);
return
(
<
Modal
isOpen
className=
"user-projects-modal"
onRequestClose=
{
onRequestClose
}
>
<
ModalTitle
className=
"user-projects-modal-title modal-header"
title=
"Add to Studio"
/>
<
SubNavigation
align=
"left"
className=
"user-projects-modal-nav"
>
<
li
className=
{
classNames
({
active
:
filter
===
Filters
.
SHARED
})
}
onClick=
{
()
=>
setFilter
(
Filters
.
SHARED
)
}
>
Shared
</
li
>
<
li
className=
{
classNames
({
active
:
filter
===
Filters
.
FAVORITED
})
}
onClick=
{
()
=>
setFilter
(
Filters
.
FAVORITED
)
}
>
Favorited
</
li
>
<
li
className=
{
classNames
({
active
:
filter
===
Filters
.
RECENT
})
}
onClick=
{
()
=>
setFilter
(
Filters
.
RECENT
)
}
>
Recent
</
li
>
</
SubNavigation
>
<
ModalInnerContent
className=
"user-projects-modal-content"
>
{
error
&&
<
div
>
Error loading
{
filter
}
:
{
error
}
</
div
>
}
<
div
className=
"user-projects-modal-grid"
>
{
items
.
map
(
project
=>
(
<
UserProjectsTile
key=
{
project
.
id
}
id=
{
project
.
id
}
title=
{
project
.
title
}
image=
{
project
.
image
}
onAdd=
{
onAdd
}
onRemove=
{
onRemove
}
/>
))
}
<
div
className=
"studio-projects-load-more"
>
{
loading
?
<
small
>
Loading...
</
small
>
:
(
moreToLoad
?
<
button
onClick=
{
()
=>
onLoadMore
(
filter
)
}
>
Load more
</
button
>
:
<
small
>
No more to load
</
small
>
)
}
</
div
>
</
div
>
</
ModalInnerContent
>
</
Modal
>
);
};
UserProjectsModal
.
propTypes
=
{
items
:
PropTypes
.
arrayOf
(
PropTypes
.
shape
({
id
:
PropTypes
.
id
,
image
:
PropTypes
.
string
,
title
:
PropTypes
.
string
})),
loading
:
PropTypes
.
bool
,
error
:
PropTypes
.
object
,
// eslint-disable-line react/forbid-prop-types
moreToLoad
:
PropTypes
.
bool
,
onLoadMore
:
PropTypes
.
func
,
onClear
:
PropTypes
.
func
,
onAdd
:
PropTypes
.
func
,
onRemove
:
PropTypes
.
func
,
onRequestClose
:
PropTypes
.
func
};
const
mapStateToProps
=
state
=>
({
...
userProjects
.
selector
(
state
)
});
const
mapDispatchToProps
=
({
onLoadMore
:
loadUserProjects
,
onClear
:
clearUserProjects
,
onAdd
:
addProject
,
onRemove
:
removeProject
});
export
default
connect
(
mapStateToProps
,
mapDispatchToProps
)(
UserProjectsModal
);
src/views/studio/modals/user-projects-modal.scss
0 → 100644
View file @
35dbcb07
@import
"../../../colors"
;
@import
"../../../frameless"
;
.user-projects-modal
{
.user-projects-modal-title
{
box-shadow
:
inset
0
-1px
0
0
$ui-blue-dark
;
background-color
:
$ui-blue
;
border-top-left-radius
:
12px
;
border-top-right-radius
:
12px
;
padding-top
:
.75rem
;
width
:
100%
;
height
:
3rem
;
}
.user-projects-modal-nav
{
padding
:
6px
12px
;
li
{
cursor
:
pointer
;
background
:
rgba
(
0
,
0
,
0
,
0
.15
);
&
.active
{
background
:
$ui-blue
;
}
}
}
.user-projects-modal-content
{
padding
:
0
30px
30px
;
background
:
#E9F1FC
;
max-height
:
80vh
;
overflow-y
:
auto
;
overscroll-behavior
:
contain
;
border-bottom-left-radius
:
12px
;
border-bottom-right-radius
:
12px
;
}
}
.studio-tile-dynamic-remove
,
.studio-tile-dynamic-add
{
position
:
absolute
;
top
:
10px
;
right
:
10px
;
width
:
32px
;
height
:
32px
;
background
:
rgba
(
0
,
0
,
0
,
0
.25
);
border
:
3px
solid
rgba
(
0
,
0
,
0
,
0
.1
);
background-clip
:
padding-box
;
color
:
white
;
border-radius
:
100%
;
font-weight
:
bold
;
margin
:
0
;
padding
:
0
;
line-height
:
32px
;
text-align
:
center
;
}
.studio-tile-dynamic-remove
{
background
:
#0FBD8C
;
background-clip
:
padding-box
;
border
:
3px
solid
rgba
(
15
,
189
,
140
,
0
.2
);
}
.user-projects-modal-grid
{
margin-top
:
12px
;
display
:
grid
;
grid-template-columns
:
repeat
(
3
,
minmax
(
0
,
1fr
));
@media
#{
$medium
}
{
/* Keep 3 columns to narrower width since it is in a modal */
&
{
grid-template-columns
:
repeat
(
2
,
minmax
(
0
,
1fr
));
}
}
@media
#{
$small
}
{
&
{
grid-template-columns
:
repeat
(
1
,
minmax
(
0
,
1fr
));
}
}
column-gap
:
14px
;
row-gap
:
14px
;
.studio-projects-load-more
{
grid-column
:
1
/
-1
;
}
.studio-project-bottom
{
padding
:
8px
10px
8px
10px
;
}
.studio-project-avatar
{
width
:
32px
;
height
:
32px
;
}
.studio-project-info
{
margin
:
0
;
}
.studio-project-title
{
font-size
:
12px
;
}
.studio-project-username
{
font-size
:
12px
;
}
}
\ No newline at end of file
src/views/studio/modals/user-projects-tile.jsx
0 → 100644
View file @
35dbcb07
/* eslint-disable react/jsx-no-bind */
import
React
,
{
useState
}
from
'
react
'
;
import
PropTypes
from
'
prop-types
'
;
import
classNames
from
'
classnames
'
;
const
UserProjectsTile
=
({
id
,
title
,
image
,
onAdd
,
onRemove
})
=>
{
const
[
submitting
,
setSubmitting
]
=
useState
(
false
);
const
[
added
,
setAdded
]
=
useState
(
false
);
const
[
error
,
setError
]
=
useState
(
null
);
const
toggle
=
()
=>
{
setSubmitting
(
true
);
setError
(
null
);
(
added
?
onRemove
(
id
)
:
onAdd
(
id
))
.
then
(()
=>
{
setAdded
(
!
added
);
setSubmitting
(
false
);
})
.
catch
(
e
=>
{
setError
(
e
);
setSubmitting
(
false
);
});
};
return
(
<
div
role=
"button"
tabIndex=
"0"
className=
{
classNames
(
'
studio-project-tile
'
,
{
'
mod-clickable
'
:
true
,
'
mod-mutating
'
:
submitting
})
}
onClick=
{
toggle
}
onKeyDown=
{
e
=>
e
.
key
===
'
Enter
'
&&
toggle
()
}
>
<
img
className=
"studio-project-image"
src=
{
image
}
/>
<
div
className=
"studio-project-bottom"
>
<
div
className=
"studio-project-title"
>
{
title
}
</
div
>
<
div
className=
{
`studio-tile-dynamic-${added ? 'remove' : 'add'}`
}
>
{
added
?
'
✔
'
:
'
+
'
}
</
div
>
{
error
&&
<
div
>
{
error
}
</
div
>
}
</
div
>
</
div
>
);
};
UserProjectsTile
.
propTypes
=
{
id
:
PropTypes
.
number
.
isRequired
,
title
:
PropTypes
.
string
.
isRequired
,
image
:
PropTypes
.
string
.
isRequired
,
onAdd
:
PropTypes
.
func
.
isRequired
,
onRemove
:
PropTypes
.
func
.
isRequired
};
export
default
UserProjectsTile
;
src/views/studio/studio-curator-inviter.jsx
View file @
35dbcb07
...
...
@@ -5,6 +5,7 @@ import {connect} from 'react-redux';
import
classNames
from
'
classnames
'
;
import
{
inviteCurator
}
from
'
./lib/studio-member-actions
'
;
import
FlexRow
from
'
../../components/flex-row/flex-row.jsx
'
;
const
StudioCuratorInviter
=
({
onSubmit
})
=>
{
const
[
value
,
setValue
]
=
useState
(
''
);
...
...
@@ -14,6 +15,7 @@ const StudioCuratorInviter = ({onSubmit}) => {
return
(
<
div
className=
"studio-adder-section"
>
<
h3
>
✦ Invite Curators
</
h3
>
<
FlexRow
>
<
input
disabled=
{
submitting
}
type=
"text"
...
...
@@ -36,6 +38,7 @@ const StudioCuratorInviter = ({onSubmit}) => {
}
}
>
Invite
</
button
>
{
error
&&
<
div
>
{
error
}
</
div
>
}
</
FlexRow
>
</
div
>
);
};
...
...
src/views/studio/studio-project-adder.jsx
View file @
35dbcb07
...
...
@@ -5,15 +5,19 @@ import {connect} from 'react-redux';
import
classNames
from
'
classnames
'
;
import
{
addProject
}
from
'
./lib/studio-project-actions
'
;
import
UserProjectsModal
from
'
./modals/user-projects-modal.jsx
'
;
import
FlexRow
from
'
../../components/flex-row/flex-row.jsx
'
;
const
StudioProjectAdder
=
({
onSubmit
})
=>
{
const
[
value
,
setValue
]
=
useState
(
''
);
const
[
submitting
,
setSubmitting
]
=
useState
(
false
);
const
[
error
,
setError
]
=
useState
(
null
);
const
[
modalOpen
,
setModalOpen
]
=
useState
(
false
);
return
(
<
div
className=
"studio-adder-section"
>
<
h3
>
✦ Add Projects
</
h3
>
<
FlexRow
>
<
input
disabled=
{
submitting
}
type=
"text"
...
...
@@ -36,6 +40,15 @@ const StudioProjectAdder = ({onSubmit}) => {
}
}
>
Add
</
button
>
{
error
&&
<
div
>
{
error
}
</
div
>
}
<
div
className=
"studio-adder-vertical-divider"
/>
<
button
className=
"button"
onClick=
{
()
=>
setModalOpen
(
true
)
}
>
Browse Projects
</
button
>
{
modalOpen
&&
<
UserProjectsModal
onRequestClose=
{
()
=>
setModalOpen
(
false
)
}
/>
}
</
FlexRow
>
</
div
>
);
};
...
...
src/views/studio/studio.jsx
View file @
35dbcb07
...
...
@@ -22,7 +22,8 @@ import {
projects
,
curators
,
managers
,
activity
activity
,
userProjects
}
from
'
./lib/redux-modules
'
;
const
{
getInitialState
,
studioReducer
}
=
require
(
'
../../redux/studio
'
);
...
...
@@ -85,6 +86,7 @@ render(
[
curators
.
key
]:
curators
.
reducer
,
[
managers
.
key
]:
managers
.
reducer
,
[
activity
.
key
]:
activity
.
reducer
,
[
userProjects
.
key
]:
userProjects
.
reducer
,
comments
:
commentsReducer
,
studio
:
studioReducer
,
studioMutations
:
studioMutationsReducer
,
...
...
src/views/studio/studio.scss
View file @
35dbcb07
...
...
@@ -63,7 +63,7 @@ $radius: 8px;
.studio-tab-nav
{
border-bottom
:
1px
solid
$active-dark-gray
;
padding-bottom
:
8px
;
li
{
background
:
$active-gray
;
}
li
{
background
:
rgba
(
0
,
0
,
0
,
0
.15
)
;
}
.active
>
li
{
background
:
$ui-blue
;
}
}
...
...
@@ -72,12 +72,12 @@ $radius: 8px;
margin-top
:
20px
;
display
:
grid
;
grid-template-columns
:
minmax
(
0
,
1fr
);
@media
#{
$medium
}
{
grid-template-columns
:
repeat
(
3
,
minmax
(
0
,
1fr
)
);
@media
#{
$medium
-and-intermediate
}
{
&
{
grid-template-columns
:
repeat
(
2
,
minmax
(
0
,
1fr
));
}
}
@media
#{
$
big
}
{
&
{
grid-template-columns
:
repeat
(
3
,
minmax
(
0
,
1fr
));
}
@media
#{
$
small
}
{
&
{
grid-template-columns
:
repeat
(
1
,
minmax
(
0
,
1fr
));
}
}
column-gap
:
30px
;
row-gap
:
20px
;
...
...
@@ -91,6 +91,9 @@ $radius: 8px;
background
:
white
;
border-radius
:
8px
;
border
:
1px
solid
$ui-border
;
position
:
relative
;
margin
:
0
;
padding
:
0
;
.studio-project-image
{
max-width
:
100%
;
...
...
@@ -123,6 +126,7 @@ $radius: 8px;
font-weight
:
700
;
font-size
:
14px
;
white-space
:
nowrap
;
overflow
:
hidden
;
text-overflow
:
ellipsis
;
}
.studio-project-username
{
...
...
@@ -130,6 +134,7 @@ $radius: 8px;
font-weight
:
700
;
font-size
:
12px
;
white-space
:
nowrap
;
overflow
:
hidden
;
text-overflow
:
ellipsis
;
}
.studio-project-remove
{
...
...
@@ -143,13 +148,12 @@ $radius: 8px;
.studio-members-grid
{
margin-top
:
20px
;
display
:
grid
;
grid-template-columns
:
minmax
(
0
,
1fr
);
@media
#{
$medium
}
{
grid-template-columns
:
repeat
(
3
,
minmax
(
0
,
1fr
));
@media
#{
$medium-and-intermediate
}
{
&
{
grid-template-columns
:
repeat
(
2
,
minmax
(
0
,
1fr
));
}
}
@media
#{
$
big
}
{
&
{
grid-template-columns
:
repeat
(
3
,
minmax
(
0
,
1fr
));
}
@media
#{
$
small
}
{
&
{
grid-template-columns
:
repeat
(
1
,
minmax
(
0
,
1fr
));
}
}
column-gap
:
30px
;
row-gap
:
20px
;
...
...
@@ -187,6 +191,7 @@ $radius: 8px;
font-weight
:
700
;
font-size
:
14px
;
white-space
:
nowrap
;
overflow
:
hidden
;
text-overflow
:
ellipsis
;
}
.studio-member-role
{
...
...
@@ -194,6 +199,7 @@ $radius: 8px;
font-weight
:
400
;
font-size
:
12px
;
white-space
:
nowrap
;
overflow
:
hidden
;
text-overflow
:
ellipsis
;
}
.studio-member-remove
,
.studio-member-promote
{
...
...
@@ -209,15 +215,19 @@ $radius: 8px;
.studio-adder-section
{
margin-top
:
20px
;
display
:
flex
;
flex-wrap
:
wrap
;
h3
{
color
:
#4C97FF
;
}
.flex-row
{
margin
:
0
-6px
;
&
>
*
{
margin
:
0
6px
;
}
}
input
{
flex-basis
:
80%
;
flex-grow
:
1
;
display
:
inline-block
;
margin
:
.5em
0
;
...
...
@@ -228,11 +238,12 @@ $radius: 8px;
}
button
{
flex-grow
:
1
;
flex-grow
:
0
;
}
input
+
button
{
margin-inline-start
:
12px
;
.studio-adder-vertical-divider
{
border
:
1px
solid
$ui-border
;
align-self
:
stretch
;
}
}
...
...
@@ -264,3 +275,7 @@ $radius: 8px;
cursor
:
wait
!
important
;
opacity
:
.5
;
}
.mod-clickable
{
cursor
:
pointer
;
}
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment