diff --git a/src/components/Assignments/Assignment/AssignmentsTable/AssignmentsTable.js b/src/components/Assignments/Assignment/AssignmentsTable/AssignmentsTable.js index b3ede40b4..495d16f8d 100644 --- a/src/components/Assignments/Assignment/AssignmentsTable/AssignmentsTable.js +++ b/src/components/Assignments/Assignment/AssignmentsTable/AssignmentsTable.js @@ -436,7 +436,7 @@ class AssignmentsTable extends Component { defaultMessage="(supervisors and students of this group)" /> } - inModal + displayAs="modal" /> )} diff --git a/src/components/widgets/Comments/CommentBox/CommentBox.js b/src/components/widgets/Comments/CommentBox/CommentBox.js index df48bf112..59ec18527 100644 --- a/src/components/widgets/Comments/CommentBox/CommentBox.js +++ b/src/components/widgets/Comments/CommentBox/CommentBox.js @@ -5,10 +5,11 @@ import { Modal } from 'react-bootstrap'; import classnames from 'classnames'; import Box from '../../Box'; +import Icon, { ChatIcon } from '../../../icons'; import styles from '../comments.less'; class CommentBox extends Component { - state = { prevCount: 0 }; + state = { prevCount: 0, panelOpen: false }; componentDidMount() { setTimeout(() => this.scrollToBottom(), 10); @@ -29,10 +30,17 @@ class CommentBox extends Component { } }; + openPanel = () => this.setState({ panelOpen: true }); + closePanel = () => this.setState({ panelOpen: false }); + renderMessages() { return (
{ this.commentsContainer = c; }}> @@ -45,10 +53,10 @@ class CommentBox extends Component { const { title = , footer, - inModal = false, + displayAs = 'box', } = this.props; - return inModal ? ( + return displayAs === 'modal' ? ( <> {title} @@ -58,6 +66,27 @@ class CommentBox extends Component {
{footer}
+ ) : displayAs === 'panel' ? ( + <> +
+
+ + {this.state.panelOpen ? ( + + ) : ( + + )} + + {this.state.panelOpen && ( + <> +
{title}
+ {this.renderMessages()} +
{footer}
+ + )} +
+
+ ) : ( {this.renderMessages()} @@ -70,7 +99,7 @@ CommentBox.propTypes = { title: PropTypes.oneOfType([PropTypes.element, PropTypes.string]), commentsCount: PropTypes.number.isRequired, footer: PropTypes.element, - inModal: PropTypes.bool, + displayAs: PropTypes.string, children: PropTypes.element, }; diff --git a/src/components/widgets/Comments/CommentThread/CommentThread.js b/src/components/widgets/Comments/CommentThread/CommentThread.js index 87c70fe7f..1499b499d 100644 --- a/src/components/widgets/Comments/CommentThread/CommentThread.js +++ b/src/components/widgets/Comments/CommentThread/CommentThread.js @@ -17,7 +17,7 @@ const CommentThread = ({ setPrivacy, refresh, deleteComment, - inModal = false, + displayAs = 'box', }) => ( } - inModal={inModal}> + displayAs={displayAs}>
{comments.map((comment, i) => comment.user.id === currentUserId ? ( @@ -76,7 +76,7 @@ CommentThread.propTypes = { setPrivacy: PropTypes.func, refresh: PropTypes.func, deleteComment: PropTypes.func, - inModal: PropTypes.bool, + displayAs: PropTypes.string, }; export default CommentThread; diff --git a/src/components/widgets/Comments/comments.less b/src/components/widgets/Comments/comments.less index b170f0182..7cb607c6d 100644 --- a/src/components/widgets/Comments/comments.less +++ b/src/components/widgets/Comments/comments.less @@ -1,4 +1,4 @@ -.fullHeight { +.modalHeight { height: calc(90vh - 200px) !important; } @@ -17,3 +17,102 @@ .iconButtonDelete { color: #f88; } + +.panel { + position: fixed; + top: 66px; + bottom: 10px; + right: 0; + width: 0.25rem; + border-bottom-left-radius: 0.25rem; + box-shadow: rgba(0, 0, 0, 0.16) 0px 3px 6px, rgba(0, 0, 0, 0.23) 0px 3px 6px; + transition: width ease 0.2s; + z-index: 2; +} + +.panel > div { + position: absolute; + top: 0; + left: 0; + bottom: 0; + right: 0; + background-color: white; + border: 1px solid rgba(0, 0, 0, 0.125); + border-right: 0; + border-bottom-left-radius: 0.25rem; + display: flex; + flex-direction: column; +} + +.panelOpen { + width: 42vw; + min-width: 400px; + border-top-left-radius: 0.25rem; +} + +.panelOpen > div { + border-top-left-radius: 0.25rem; +} + +.panel span.panelIcon { + position: absolute; + top: -1px; + left: -3rem; + width: 3rem; + height: 3rem; + font-size: 1.3rem; + text-align: center; + line-height: 3rem; + + background-color: white; + border: 1px solid rgba(0, 0, 0, 0.125); + border-right: 0; + border-top-left-radius: 1rem; + border-bottom-left-radius: 1rem; + z-index: 3; + + transition: top ease 1s; +} + +.panel::after { + position: absolute; + content: ""; + top: 0; + left: calc(1px - 3rem); + width: 3rem; + height: 3rem; + background-color: white; + display: block; + border-top-left-radius: 1rem; + border-bottom-left-radius: 1rem; + box-shadow: rgba(0, 0, 0, 0.16) 0px 3px 6px, rgba(0, 0, 0, 0.23) 0px 3px 6px; + z-index: -1; + transition: top ease 1s; +} + +.panelOpen span.panelIcon { + top: calc(50% - 1.5rem); +} + +.panelOpen::after { + top: calc(50% - 1.5rem); +} + +.panel span.panelIcon > * { + cursor: pointer; +} + +.panel div.header { + font-size: 1.1rem; + padding: 0.75rem 1.25rem; + border-bottom: 1px solid rgba(0, 0, 0, 0.125); +} + +.panel div.body { + flex-grow: 1; +} + +.panel div.footer { + border-top: 1px solid rgba(0, 0, 0, 0.125); + padding: 0.75rem 1.25rem; +} \ No newline at end of file diff --git a/src/containers/CommentThreadContainer/CommentThreadContainer.js b/src/containers/CommentThreadContainer/CommentThreadContainer.js index dc6bc49b1..72456c257 100644 --- a/src/containers/CommentThreadContainer/CommentThreadContainer.js +++ b/src/containers/CommentThreadContainer/CommentThreadContainer.js @@ -46,7 +46,7 @@ class CommentThreadContainer extends Component { setPrivacy, refresh, deleteComment, - inModal = false, + displayAs = 'box', } = this.props; return ( @@ -62,7 +62,7 @@ class CommentThreadContainer extends Component { repostComment={repostComment} refresh={refresh} deleteComment={deleteComment} - inModal={inModal} + displayAs={displayAs} /> )} @@ -74,7 +74,7 @@ CommentThreadContainer.propTypes = { threadId: PropTypes.string.isRequired, title: PropTypes.oneOfType([PropTypes.element, PropTypes.string]), additionalPublicSwitchNote: PropTypes.oneOfType([PropTypes.string, PropTypes.element]), - inModal: PropTypes.bool, + displayAs: PropTypes.string, thread: PropTypes.object, user: PropTypes.object, addComment: PropTypes.func.isRequired, diff --git a/src/pages/AssignmentSolutions/AssignmentSolutions.js b/src/pages/AssignmentSolutions/AssignmentSolutions.js index 5a7f40333..a7899a924 100644 --- a/src/pages/AssignmentSolutions/AssignmentSolutions.js +++ b/src/pages/AssignmentSolutions/AssignmentSolutions.js @@ -530,7 +530,7 @@ class AssignmentSolutions extends Component { defaultMessage="(supervisors and students of this group)" /> } - inModal + displayAs="modal" /> diff --git a/src/pages/SolutionSourceCodes/SolutionSourceCodes.js b/src/pages/SolutionSourceCodes/SolutionSourceCodes.js index cb67656eb..ae7ff40f9 100644 --- a/src/pages/SolutionSourceCodes/SolutionSourceCodes.js +++ b/src/pages/SolutionSourceCodes/SolutionSourceCodes.js @@ -22,12 +22,13 @@ import { StopIcon, SwapIcon, } from '../../components/icons'; -import SolutionActionsContainer from '../../containers/SolutionActionsContainer'; import SourceCodeBox from '../../components/Solutions/SourceCodeBox'; import ReviewSummary from '../../components/Solutions/ReviewSummary'; import RecentlyVisited from '../../components/Solutions/RecentlyVisited'; import { registerSolutionVisit } from '../../components/Solutions/RecentlyVisited/functions'; import Callout from '../../components/widgets/Callout'; +import SolutionActionsContainer from '../../containers/SolutionActionsContainer'; +import CommentThreadContainer from '../../containers/CommentThreadContainer'; import { fetchRuntimeEnvironments } from '../../redux/modules/runtimeEnvironments'; import { fetchAssignmentIfNeeded } from '../../redux/modules/assignments'; @@ -67,7 +68,13 @@ const wrapInArray = defaultMemoize(entry => [entry]); const localStorageDiffMappingsKey = 'SolutionSourceCodes.diffMappings.'; class SolutionSourceCodes extends Component { - state = { diffDialogOpen: false, mappingDialogOpenFile: null, mappingDialogDiffWith: null, diffMappings: {} }; + state = { + diffDialogOpen: false, + mappingDialogOpenFile: null, + mappingDialogDiffWith: null, + diffMappings: {}, + commentsOpen: false, + }; static loadAsync = ({ solutionId, assignmentId, secondSolutionId }, dispatch) => Promise.all([ @@ -147,6 +154,9 @@ class SolutionSourceCodes extends Component { this.setState({ diffDialogOpen: false, mappingDialogOpenFile: null, mappingDialogDiffWith: null }); }; + openComments = () => this.setState({ commentsOpen: true }); + closeComments = () => this.setState({ commentsOpen: false }); + selectDiffSolution = id => { const { match: { @@ -593,6 +603,17 @@ class SolutionSourceCodes extends Component { )} + + } + displayAs="panel" + /> +