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
f61047dd
Unverified
Commit
f61047dd
authored
May 11, 2021
by
Paul Kaplan
Committed by
GitHub
May 11, 2021
Browse files
Options
Browse Files
Download
Plain Diff
Merge branch 'develop' into add-studio-activity-pagination
parents
0fdf5433
051fc107
Changes
13
Hide whitespace changes
Inline
Side-by-side
Showing
13 changed files
with
443 additions
and
58 deletions
+443
-58
src/redux/infinite-list.js
src/redux/infinite-list.js
+7
-5
src/views/studio/l10n.json
src/views/studio/l10n.json
+5
-0
src/views/studio/lib/redux-modules.js
src/views/studio/lib/redux-modules.js
+3
-1
src/views/studio/lib/studio-project-actions.js
src/views/studio/lib/studio-project-actions.js
+1
-1
src/views/studio/lib/user-projects-actions.js
src/views/studio/lib/user-projects-actions.js
+59
-0
src/views/studio/modals/user-projects-modal.jsx
src/views/studio/modals/user-projects-modal.jsx
+121
-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
+58
-0
src/views/studio/studio-curator-inviter.jsx
src/views/studio/studio-curator-inviter.jsx
+19
-16
src/views/studio/studio-project-adder.jsx
src/views/studio/studio-project-adder.jsx
+29
-16
src/views/studio/studio.jsx
src/views/studio/studio.jsx
+3
-1
src/views/studio/studio.scss
src/views/studio/studio.scss
+32
-17
test/unit/redux/infinite-list.test.js
test/unit/redux/infinite-list.test.js
+14
-1
No files found.
src/redux/infinite-list.js
View file @
f61047dd
...
@@ -27,17 +27,16 @@
...
@@ -27,17 +27,16 @@
*/
*/
const
InfiniteList
=
key
=>
{
const
InfiniteList
=
key
=>
{
const
initialState
=
{
const
getInitialState
=
()
=>
(
{
items
:
[],
items
:
[],
offset
:
0
,
error
:
null
,
error
:
null
,
loading
:
true
,
loading
:
true
,
moreToLoad
:
false
moreToLoad
:
false
};
}
)
;
const
reducer
=
(
state
,
action
)
=>
{
const
reducer
=
(
state
,
action
)
=>
{
if
(
typeof
state
===
'
undefined
'
)
{
if
(
typeof
state
===
'
undefined
'
)
{
state
=
initialState
;
state
=
getInitialState
()
;
}
}
switch
(
action
.
type
)
{
switch
(
action
.
type
)
{
...
@@ -81,6 +80,8 @@ const InfiniteList = key => {
...
@@ -81,6 +80,8 @@ const InfiniteList = key => {
loading
:
false
,
loading
:
false
,
moreToLoad
:
false
moreToLoad
:
false
};
};
case
`
${
key
}
_CLEAR`
:
return
getInitialState
();
default
:
default
:
return
state
;
return
state
;
}
}
...
@@ -92,7 +93,8 @@ const InfiniteList = key => {
...
@@ -92,7 +93,8 @@ const InfiniteList = key => {
replace
:
(
index
,
item
)
=>
({
type
:
`
${
key
}
_REPLACE`
,
index
,
item
}),
replace
:
(
index
,
item
)
=>
({
type
:
`
${
key
}
_REPLACE`
,
index
,
item
}),
error
:
error
=>
({
type
:
`
${
key
}
_ERROR`
,
error
}),
error
:
error
=>
({
type
:
`
${
key
}
_ERROR`
,
error
}),
loading
:
()
=>
({
type
:
`
${
key
}
_LOADING`
}),
loading
:
()
=>
({
type
:
`
${
key
}
_LOADING`
}),
append
:
(
items
,
moreToLoad
)
=>
({
type
:
`
${
key
}
_APPEND`
,
items
,
moreToLoad
})
append
:
(
items
,
moreToLoad
)
=>
({
type
:
`
${
key
}
_APPEND`
,
items
,
moreToLoad
}),
clear
:
()
=>
({
type
:
`
${
key
}
_CLEAR`
})
};
};
const
selector
=
state
=>
state
[
key
];
const
selector
=
state
=>
state
[
key
];
...
...
src/views/studio/l10n.json
View file @
f61047dd
...
@@ -11,6 +11,7 @@
...
@@ -11,6 +11,7 @@
"studio.projectsHeader"
:
"Projects"
,
"studio.projectsHeader"
:
"Projects"
,
"studio.addProjectsHeader"
:
"Add Projects"
,
"studio.addProjectsHeader"
:
"Add Projects"
,
"studio.addProject"
:
"Add"
,
"studio.addProject"
:
"Add"
,
"studio.browseProjects"
:
"Browse Projects"
,
"studio.creatorRole"
:
"Studio Creator"
,
"studio.creatorRole"
:
"Studio Creator"
,
...
@@ -26,6 +27,10 @@
...
@@ -26,6 +27,10 @@
"studio.commentsHeader"
:
"Comments"
,
"studio.commentsHeader"
:
"Comments"
,
"studio.sharedFilter"
:
"Shared"
,
"studio.favoritedFilter"
:
"Favorited"
,
"studio.recentFilter"
:
"Recent"
,
"studio.activityAddProjectToStudio"
:
"{profileLink} added the project {projectLink}"
,
"studio.activityAddProjectToStudio"
:
"{profileLink} added the project {projectLink}"
,
"studio.activityRemoveProjectStudio"
:
"{profileLink} removed the project {projectLink}"
,
"studio.activityRemoveProjectStudio"
:
"{profileLink} removed the project {projectLink}"
,
"studio.activityUpdateStudio"
:
"{profileLink} made edits to the title, thumbnail, or description"
,
"studio.activityUpdateStudio"
:
"{profileLink} made edits to the title, thumbnail, or description"
,
...
...
src/views/studio/lib/redux-modules.js
View file @
f61047dd
...
@@ -5,6 +5,8 @@ const curators = InfiniteList('curators');
...
@@ -5,6 +5,8 @@ const curators = InfiniteList('curators');
const
managers
=
InfiniteList
(
'
managers
'
);
const
managers
=
InfiniteList
(
'
managers
'
);
const
activity
=
InfiniteList
(
'
activity
'
);
const
activity
=
InfiniteList
(
'
activity
'
);
const
userProjects
=
InfiniteList
(
'
user-projects
'
);
export
{
export
{
projects
,
curators
,
managers
,
activity
projects
,
curators
,
managers
,
activity
,
userProjects
};
};
src/views/studio/lib/studio-project-actions.js
View file @
f61047dd
...
@@ -50,7 +50,7 @@ const loadProjects = () => ((dispatch, getState) => {
...
@@ -50,7 +50,7 @@ const loadProjects = () => ((dispatch, getState) => {
*/
*/
const
generateProjectListItem
=
(
postBody
,
infoBody
)
=>
({
const
generateProjectListItem
=
(
postBody
,
infoBody
)
=>
({
// Fields from the POST to add the project to the studio
// Fields from the POST to add the project to the studio
id
:
p
ostBody
.
projectId
,
id
:
p
arseInt
(
postBody
.
projectId
,
10
)
,
actor_id
:
postBody
.
actorId
,
actor_id
:
postBody
.
actorId
,
// Fields from followup GET for more project info
// Fields from followup GET for more project info
title
:
infoBody
.
title
,
title
:
infoBody
.
title
,
...
...
src/views/studio/lib/user-projects-actions.js
0 → 100644
View file @
f61047dd
import
keyMirror
from
'
keymirror
'
;
import
api
from
'
../../../lib/api
'
;
import
{
selectUsername
}
from
'
../../../redux/session
'
;
import
{
userProjects
,
projects
}
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
));
const
moreToLoad
=
body
.
length
===
projectsPerPage
;
const
studioProjectIds
=
projects
.
selector
(
getState
()).
items
.
map
(
item
=>
item
.
id
);
const
loadedProjects
=
body
.
map
(
item
=>
Object
.
assign
(
item
,
{
inStudio
:
studioProjectIds
.
indexOf
(
item
.
id
)
!==
-
1
}));
dispatch
(
userProjects
.
actions
.
append
(
loadedProjects
,
moreToLoad
));
});
});
// 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 @
f61047dd
/* 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
{
FormattedMessage
}
from
'
react-intl
'
;
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
)
}
>
<
FormattedMessage
id=
"studio.sharedFilter"
/>
</
li
>
<
li
className=
{
classNames
({
active
:
filter
===
Filters
.
FAVORITED
})
}
onClick=
{
()
=>
setFilter
(
Filters
.
FAVORITED
)
}
>
<
FormattedMessage
id=
"studio.favoritedFilter"
/>
</
li
>
<
li
className=
{
classNames
({
active
:
filter
===
Filters
.
RECENT
})
}
onClick=
{
()
=>
setFilter
(
Filters
.
RECENT
)
}
>
<
FormattedMessage
id=
"studio.recentFilter"
/>
</
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
}
inStudio=
{
project
.
inStudio
}
onAdd=
{
onAdd
}
onRemove=
{
onRemove
}
/>
))
}
<
div
className=
"studio-projects-load-more"
>
{
loading
?
<
small
>
Loading...
</
small
>
:
(
moreToLoad
?
<
button
onClick=
{
()
=>
onLoadMore
(
filter
)
}
>
<
FormattedMessage
id=
"general.loadMore"
/>
</
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
,
inStudio
:
PropTypes
.
bool
})),
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 @
f61047dd
@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
;
}
}
src/views/studio/modals/user-projects-tile.jsx
0 → 100644
View file @
f61047dd
/* 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
,
inStudio
,
onAdd
,
onRemove
})
=>
{
const
[
submitting
,
setSubmitting
]
=
useState
(
false
);
const
[
added
,
setAdded
]
=
useState
(
inStudio
);
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
,
inStudio
:
PropTypes
.
bool
.
isRequired
,
onAdd
:
PropTypes
.
func
.
isRequired
,
onRemove
:
PropTypes
.
func
.
isRequired
};
export
default
UserProjectsTile
;
src/views/studio/studio-curator-inviter.jsx
View file @
f61047dd
...
@@ -6,6 +6,7 @@ import classNames from 'classnames';
...
@@ -6,6 +6,7 @@ import classNames from 'classnames';
import
{
FormattedMessage
}
from
'
react-intl
'
;
import
{
FormattedMessage
}
from
'
react-intl
'
;
import
{
inviteCurator
}
from
'
./lib/studio-member-actions
'
;
import
{
inviteCurator
}
from
'
./lib/studio-member-actions
'
;
import
FlexRow
from
'
../../components/flex-row/flex-row.jsx
'
;
const
StudioCuratorInviter
=
({
onSubmit
})
=>
{
const
StudioCuratorInviter
=
({
onSubmit
})
=>
{
const
[
value
,
setValue
]
=
useState
(
''
);
const
[
value
,
setValue
]
=
useState
(
''
);
...
@@ -22,22 +23,24 @@ const StudioCuratorInviter = ({onSubmit}) => {
...
@@ -22,22 +23,24 @@ const StudioCuratorInviter = ({onSubmit}) => {
return
(
return
(
<
div
className=
"studio-adder-section"
>
<
div
className=
"studio-adder-section"
>
<
h3
><
FormattedMessage
id=
"studio.inviteCuratorsHeader"
/></
h3
>
<
h3
><
FormattedMessage
id=
"studio.inviteCuratorsHeader"
/></
h3
>
<
input
<
FlexRow
>
disabled=
{
submitting
}
<
input
type=
"text"
disabled=
{
submitting
}
placeholder=
"<username>"
type=
"text"
value=
{
value
}
placeholder=
"<username>"
onKeyDown=
{
e
=>
e
.
key
===
'
Enter
'
&&
submit
()
}
value=
{
value
}
onChange=
{
e
=>
setValue
(
e
.
target
.
value
)
}
onKeyDown=
{
e
=>
e
.
key
===
'
Enter
'
&&
submit
()
}
/>
onChange=
{
e
=>
setValue
(
e
.
target
.
value
)
}
<
button
/>
className=
{
classNames
(
'
button
'
,
{
<
button
'
mod-mutating
'
:
submitting
className=
{
classNames
(
'
button
'
,
{
})
}
'
mod-mutating
'
:
submitting
disabled=
{
submitting
}
})
}
onClick=
{
submit
}
disabled=
{
submitting
}
><
FormattedMessage
id=
"studio.inviteCurator"
/></
button
>
onClick=
{
submit
}
{
error
&&
<
div
>
{
error
}
</
div
>
}
><
FormattedMessage
id=
"studio.inviteCurator"
/></
button
>
{
error
&&
<
div
>
{
error
}
</
div
>
}
</
FlexRow
>
</
div
>
</
div
>
);
);
};
};
...
...
src/views/studio/studio-project-adder.jsx
View file @
f61047dd
...
@@ -6,11 +6,14 @@ import classNames from 'classnames';
...
@@ -6,11 +6,14 @@ import classNames from 'classnames';
import
{
FormattedMessage
}
from
'
react-intl
'
;
import
{
FormattedMessage
}
from
'
react-intl
'
;
import
{
addProject
}
from
'
./lib/studio-project-actions
'
;
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
StudioProjectAdder
=
({
onSubmit
})
=>
{
const
[
value
,
setValue
]
=
useState
(
''
);
const
[
value
,
setValue
]
=
useState
(
''
);
const
[
submitting
,
setSubmitting
]
=
useState
(
false
);
const
[
submitting
,
setSubmitting
]
=
useState
(
false
);
const
[
error
,
setError
]
=
useState
(
null
);
const
[
error
,
setError
]
=
useState
(
null
);
const
[
modalOpen
,
setModalOpen
]
=
useState
(
false
);
const
submit
=
()
=>
{
const
submit
=
()
=>
{
setSubmitting
(
true
);
setSubmitting
(
true
);
setError
(
null
);
setError
(
null
);
...
@@ -22,22 +25,32 @@ const StudioProjectAdder = ({onSubmit}) => {
...
@@ -22,22 +25,32 @@ const StudioProjectAdder = ({onSubmit}) => {
return
(
return
(
<
div
className=
"studio-adder-section"
>
<
div
className=
"studio-adder-section"
>
<
h3
><
FormattedMessage
id=
"studio.addProjectsHeader"
/></
h3
>
<
h3
><
FormattedMessage
id=
"studio.addProjectsHeader"
/></
h3
>
<
input
<
FlexRow
>
disabled=
{
submitting
}
<
input
type=
"text"
disabled=
{
submitting
}
placeholder=
"<project id>"
type=
"text"
value=
{
value
}
placeholder=
"<project id>"
onKeyDown=
{
e
=>
e
.
key
===
'
Enter
'
&&
submit
()
}
value=
{
value
}
onChange=
{
e
=>
setValue
(
e
.
target
.
value
)
}
onKeyDown=
{
e
=>
e
.
key
===
'
Enter
'
&&
submit
()
}
/>
onChange=
{
e
=>
setValue
(
e
.
target
.
value
)
}
<
button
/>
className=
{
classNames
(
'
button
'
,
{
<
button
'
mod-mutating
'
:
submitting
className=
{
classNames
(
'
button
'
,
{
})
}
'
mod-mutating
'
:
submitting
disabled=
{
submitting
}
})
}
onClick=
{
submit
}
disabled=
{
submitting
}
><
FormattedMessage
id=
"studio.addProject"
/></
button
>
onClick=
{
submit
}
{
error
&&
<
div
>
{
error
}
</
div
>
}
><
FormattedMessage
id=
"studio.addProject"
/></
button
>
{
error
&&
<
div
>
{
error
}
</
div
>
}
<
div
className=
"studio-adder-vertical-divider"
/>
<
button
className=
"button"
onClick=
{
()
=>
setModalOpen
(
true
)
}
>
<
FormattedMessage
id=
"studio.browseProjects"
/>
</
button
>
{
modalOpen
&&
<
UserProjectsModal
onRequestClose=
{
()
=>
setModalOpen
(
false
)
}
/>
}
</
FlexRow
>
</
div
>
</
div
>
);
);
};
};
...
...
src/views/studio/studio.jsx
View file @
f61047dd
...
@@ -27,7 +27,8 @@ import {
...
@@ -27,7 +27,8 @@ import {
projects
,
projects
,
curators
,
curators
,
managers
,
managers
,
activity
activity
,
userProjects
}
from
'
./lib/redux-modules
'
;
}
from
'
./lib/redux-modules
'
;
const
{
getInitialState
,
studioReducer
,
selectStudioLoadFailed
}
=
require
(
'
../../redux/studio
'
);
const
{
getInitialState
,
studioReducer
,
selectStudioLoadFailed
}
=
require
(
'
../../redux/studio
'
);
...
@@ -102,6 +103,7 @@ render(
...
@@ -102,6 +103,7 @@ render(
[
curators
.
key
]:
curators
.
reducer
,
[
curators
.
key
]:
curators
.
reducer
,
[
managers
.
key
]:
managers
.
reducer
,
[
managers
.
key
]:
managers
.
reducer
,
[
activity
.
key
]:
activity
.
reducer
,
[
activity
.
key
]:
activity
.
reducer
,
[
userProjects
.
key
]:
userProjects
.
reducer
,
comments
:
commentsReducer
,
comments
:
commentsReducer
,
studio
:
studioReducer
,
studio
:
studioReducer
,
studioMutations
:
studioMutationsReducer
,
studioMutations
:
studioMutationsReducer
,
...
...
src/views/studio/studio.scss
View file @
f61047dd
...
@@ -5,7 +5,7 @@ $radius: 8px;
...
@@ -5,7 +5,7 @@ $radius: 8px;
.studio-page
{
.studio-page
{
background-color
:
#E9F1FC
;
background-color
:
#E9F1FC
;
#view
{
#view
{
/* Reset some defaults on width and margin */
/* Reset some defaults on width and margin */
background-color
:
transparent
;
background-color
:
transparent
;
...
@@ -63,7 +63,7 @@ $radius: 8px;
...
@@ -63,7 +63,7 @@ $radius: 8px;
.studio-tab-nav
{
.studio-tab-nav
{
border-bottom
:
1px
solid
$active-dark-gray
;
border-bottom
:
1px
solid
$active-dark-gray
;
padding-bottom
:
8px
;
padding-bottom
:
8px
;
li
{
background
:
$active-gray
;
}
li
{
background
:
rgba
(
0
,
0
,
0
,
0
.15
)
;
}
.active
>
li
{
background
:
$ui-blue
;
}
.active
>
li
{
background
:
$ui-blue
;
}
}
}
...
@@ -72,12 +72,12 @@ $radius: 8px;
...
@@ -72,12 +72,12 @@ $radius: 8px;
margin-top
:
20px
;
margin-top
:
20px
;
display
:
grid
;
display
:
grid
;
grid-template-columns
:
minmax
(
0
,
1fr
);
grid-template-columns
:
repeat
(
3
,
minmax
(
0
,
1fr
)
);
@media
#{
$medium
}
{
@media
#{
$medium
-and-intermediate
}
{
&
{
grid-template-columns
:
repeat
(
2
,
minmax
(
0
,
1fr
));
}
&
{
grid-template-columns
:
repeat
(
2
,
minmax
(
0
,
1fr
));
}
}
}
@media
#{
$
big
}
{
@media
#{
$
small
}
{
&
{
grid-template-columns
:
repeat
(
3
,
minmax
(
0
,
1fr
));
}
&
{
grid-template-columns
:
repeat
(
1
,
minmax
(
0
,
1fr
));
}
}
}
column-gap
:
30px
;
column-gap
:
30px
;
row-gap
:
20px
;
row-gap
:
20px
;
...
@@ -91,6 +91,9 @@ $radius: 8px;
...
@@ -91,6 +91,9 @@ $radius: 8px;
background
:
white
;
background
:
white
;
border-radius
:
8px
;
border-radius
:
8px
;
border
:
1px
solid
$ui-border
;
border
:
1px
solid
$ui-border
;
position
:
relative
;
margin
:
0
;
padding
:
0
;
.studio-project-image
{
.studio-project-image
{
max-width
:
100%
;
max-width
:
100%
;
...
@@ -123,6 +126,7 @@ $radius: 8px;
...
@@ -123,6 +126,7 @@ $radius: 8px;
font-weight
:
700
;
font-weight
:
700
;
font-size
:
14px
;
font-size
:
14px
;
white-space
:
nowrap
;
white-space
:
nowrap
;
overflow
:
hidden
;
text-overflow
:
ellipsis
;
text-overflow
:
ellipsis
;
}
}
.studio-project-username
{
.studio-project-username
{
...
@@ -130,6 +134,7 @@ $radius: 8px;
...
@@ -130,6 +134,7 @@ $radius: 8px;
font-weight
:
700
;
font-weight
:
700
;
font-size
:
12px
;
font-size
:
12px
;
white-space
:
nowrap
;
white-space
:
nowrap
;
overflow
:
hidden
;
text-overflow
:
ellipsis
;
text-overflow
:
ellipsis
;
}
}
.studio-project-remove
{
.studio-project-remove
{
...
@@ -143,13 +148,12 @@ $radius: 8px;
...
@@ -143,13 +148,12 @@ $radius: 8px;
.studio-members-grid
{
.studio-members-grid
{
margin-top
:
20px
;
margin-top
:
20px
;
display
:
grid
;
display
:
grid
;
grid-template-columns
:
repeat
(
3
,
minmax
(
0
,
1fr
));
grid-template-columns
:
minmax
(
0
,
1fr
);
@media
#{
$medium-and-intermediate
}
{
@media
#{
$medium
}
{
&
{
grid-template-columns
:
repeat
(
2
,
minmax
(
0
,
1fr
));
}
&
{
grid-template-columns
:
repeat
(
2
,
minmax
(
0
,
1fr
));
}
}
}
@media
#{
$
big
}
{
@media
#{
$
small
}
{
&
{
grid-template-columns
:
repeat
(
3
,
minmax
(
0
,
1fr
));
}
&
{
grid-template-columns
:
repeat
(
1
,
minmax
(
0
,
1fr
));
}
}
}
column-gap
:
30px
;
column-gap
:
30px
;
row-gap
:
20px
;
row-gap
:
20px
;
...
@@ -187,6 +191,7 @@ $radius: 8px;
...
@@ -187,6 +191,7 @@ $radius: 8px;
font-weight
:
700
;
font-weight
:
700
;
font-size
:
14px
;
font-size
:
14px
;
white-space
:
nowrap
;
white-space
:
nowrap
;
overflow
:
hidden
;
text-overflow
:
ellipsis
;
text-overflow
:
ellipsis
;
}
}
.studio-member-role
{
.studio-member-role
{
...
@@ -194,6 +199,7 @@ $radius: 8px;
...
@@ -194,6 +199,7 @@ $radius: 8px;
font-weight
:
400
;
font-weight
:
400
;
font-size
:
12px
;
font-size
:
12px
;
white-space
:
nowrap
;
white-space
:
nowrap
;
overflow
:
hidden
;
text-overflow
:
ellipsis
;
text-overflow
:
ellipsis
;
}
}
.studio-member-remove
,
.studio-member-promote
{
.studio-member-remove
,
.studio-member-promote
{
...
@@ -209,15 +215,19 @@ $radius: 8px;
...
@@ -209,15 +215,19 @@ $radius: 8px;
.studio-adder-section
{
.studio-adder-section
{
margin-top
:
20px
;
margin-top
:
20px
;
display
:
flex
;
flex-wrap
:
wrap
;
h3
{
h3
{
color
:
#4C97FF
;
color
:
#4C97FF
;
}
}
.flex-row
{
margin
:
0
-6px
;
&
>
*
{
margin
:
0
6px
;
}
}
input
{
input
{
flex-basis
:
80%
;
flex-grow
:
1
;
flex-grow
:
1
;
display
:
inline-block
;
display
:
inline-block
;
margin
:
.5em
0
;
margin
:
.5em
0
;
...
@@ -228,11 +238,12 @@ $radius: 8px;
...
@@ -228,11 +238,12 @@ $radius: 8px;
}
}
button
{
button
{
flex-grow
:
1
;
flex-grow
:
0
;
}
}
input
+
button
{
.studio-adder-vertical-divider
{
margin-inline-start
:
12px
;
border
:
1px
solid
$ui-border
;
align-self
:
stretch
;
}
}
}
}
...
@@ -284,3 +295,7 @@ $radius: 8px;
...
@@ -284,3 +295,7 @@ $radius: 8px;
cursor
:
wait
!
important
;
cursor
:
wait
!
important
;
opacity
:
.5
;
opacity
:
.5
;
}
}
.mod-clickable
{
cursor
:
pointer
;
}
test/unit/redux/infinite-list.test.js
View file @
f61047dd
...
@@ -106,6 +106,19 @@ describe('Infinite List redux module', () => {
...
@@ -106,6 +106,19 @@ describe('Infinite List redux module', () => {
});
});
});
});
describe
(
'
CLEAR
'
,
()
=>
{
test
(
'
resets everything back to the initial state
'
,
()
=>
{
const
state
=
{
error
:
new
Error
(),
items
:
[
1
,
2
,
3
],
loading
:
'
something not initial
'
,
moreToLoad
:
'
something not initial
'
};
const
newState
=
module
.
reducer
(
state
,
module
.
actions
.
clear
());
expect
(
newState
).
toEqual
(
initialState
);
});
});
describe
(
'
ERROR
'
,
()
=>
{
describe
(
'
ERROR
'
,
()
=>
{
let
action
;
let
action
;
let
error
=
new
Error
();
let
error
=
new
Error
();
...
@@ -141,7 +154,7 @@ describe('Infinite List redux module', () => {
...
@@ -141,7 +154,7 @@ describe('Infinite List redux module', () => {
describe
(
'
selector
'
,
()
=>
{
describe
(
'
selector
'
,
()
=>
{
test
(
'
will return the slice of state defined by the key
'
,
()
=>
{
test
(
'
will return the slice of state defined by the key
'
,
()
=>
{
const
state
=
{
const
state
=
{
[
module
.
key
]:
module
.
reducer
(
undefined
,
{})
// eslint-disable-line no-undefined
[
module
.
key
]:
initialState
};
};
expect
(
module
.
selector
(
state
)).
toBe
(
initialState
);
expect
(
module
.
selector
(
state
)).
toBe
(
initialState
);
});
});
...
...
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