diff --git a/frontend/config/index.js b/frontend/config/index.js index 2c458f66802f5f8d206f4f136a0b5d52b59b4c21..91cda89ee4dffe53254ba613294b6df8b9d1bc4b 100644 --- a/frontend/config/index.js +++ b/frontend/config/index.js @@ -17,7 +17,7 @@ module.exports = { // Surge or Netlify already gzip all static assets for you. // Before setting to `true`, make sure to: // npm install --save-dev compression-webpack-plugin - productionGzip: false, + productionGzip: true, productionGzipExtensions: ['js', 'css'], // Run the build command with an extra argument to // View the bundle analyzer report after build finishes: diff --git a/frontend/index.html b/frontend/index.html index fb63c5378340aee23aac55a9168ec7067cb63066..c2b9135d7c4243df5b2e5ae26ecb8616aeae41f8 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -2,7 +2,7 @@ <html> <head> <meta charset="utf-8"> - <title>frontend</title> + <title>Grady</title> </head> <body> <div id="app"></div> diff --git a/frontend/package.json b/frontend/package.json index df857580b0d73ecbe6c26b4116cac54a204a78b6..9f4245d5f207a1c6fca82b536c426d6f8460c366 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -14,6 +14,7 @@ }, "dependencies": { "axios": "^0.17.0", + "google-code-prettify": "^1.0.5", "material-design-icons": "^3.0.1", "vue": "^2.5.2", "vue-router": "^3.0.1", @@ -32,6 +33,7 @@ "babel-register": "^6.22.0", "chai": "^4.1.2", "chalk": "^2.0.1", + "compression-webpack-plugin": "^1.0.1", "connect-history-api-fallback": "^1.3.0", "copy-webpack-plugin": "^4.0.1", "cross-env": "^5.0.1", diff --git a/frontend/src/App.vue b/frontend/src/App.vue index 64fcd76cf6a10610db451677e80912d837ffaeae..85b68867dad83e4364f2ddf053b73f3e9d7d6863 100644 --- a/frontend/src/App.vue +++ b/frontend/src/App.vue @@ -1,9 +1,9 @@ <template> - <div id="app"> - <v-app> + <v-app> + <div id="app"> <router-view/> - </v-app> - </div> + </div> + </v-app> </template> <script> @@ -11,11 +11,8 @@ name: 'app', components: { } -} + } </script> <style> -#app { - -} </style> diff --git a/frontend/src/components/submission_notes/AnnotatedSubmission.vue b/frontend/src/components/submission_notes/AnnotatedSubmission.vue new file mode 100644 index 0000000000000000000000000000000000000000..6b7d35d83f9c995dc5d9e19f73b13737ba9b0b6f --- /dev/null +++ b/frontend/src/components/submission_notes/AnnotatedSubmission.vue @@ -0,0 +1,103 @@ +<template> + <table> + <tr v-for="(code, index) in submission" :key="index"> + <td class="line-number-cell"> + <v-tooltip left close-delay="20" color="transparent" content-class="comment-icon"> + <v-btn block class="line-number-btn" slot="activator" @click="toggleEditorOnLine(index)">{{ index }}</v-btn> + <v-icon small color="indigo accent-3" class="comment-icon">fa-comment</v-icon> + </v-tooltip> + </td> + <td> + <pre class="prettyprint"><code class="lang-c"> {{ code }}</code></pre> + <feedback-comment + v-if="feedback[index] && !showEditorOnLine[index]" + @click="toggleEditorOnLine(index)">{{ feedback[index] }} + </feedback-comment> + <comment-form + v-if="showEditorOnLine[index]" + @collapseFeedbackForm="showEditorOnLine[index] = false" + :feedback="feedback[index]" + :index="index"> + </comment-form> + </td> + </tr> + </table> +</template> + + +<script> + import {mapGetters, mapState} from 'vuex' + import CommentForm from '@/components/submission_notes/FeedbackForm.vue' + import FeedbackComment from '@/components/submission_notes/FeedbackComment.vue' + + export default { + components: { + FeedbackComment, + CommentForm}, + name: 'annotated-submission', + beforeCreate () { + this.$store.dispatch('getFeedback', 0) + this.$store.dispatch('getSubmission', 0) + }, + computed: { + ...mapState({ + feedback: state => state.submissionNotes.feedback + }), + ...mapGetters(['submission']) + }, + data: function () { + return { + showEditorOnLine: { } + } + }, + methods: { + toggleEditorOnLine (lineIndex) { + this.$set(this.showEditorOnLine, lineIndex, !this.showEditorOnLine[lineIndex]) + } + }, + mounted () { + window.PR.prettyPrint() + } + } +</script> + + +<style scoped> + + table { + table-layout: auto; + border-collapse: collapse; + border-width: 0px; + } + + td { + /*white-space: nowrap;*/ + /*border: 1px solid green;*/ + } + + .line-number-cell { + padding-left: 50px; + vertical-align: top; + } + + pre.prettyprint { + padding: 0px; + border: 0px; + } + + code { + width: 100%; + } + + + .line-number-btn { + height: fit-content; + min-width: fit-content; + margin: 0; + } + + .comment-icon { + border: 0px; + } + +</style> diff --git a/frontend/src/components/submission_notes/FeedbackComment.vue b/frontend/src/components/submission_notes/FeedbackComment.vue new file mode 100644 index 0000000000000000000000000000000000000000..a63b4de6a32267608763b9f999f5088adf462e71 --- /dev/null +++ b/frontend/src/components/submission_notes/FeedbackComment.vue @@ -0,0 +1,54 @@ +<template> + <div class="dialogbox"> + <div class="body"> + <span class="tip tip-up"></span> + <div class="message"> + <slot></slot> + </div> + </div> + </div> +</template> + + +<script> + export default { + name: 'feedback-comment' + } +</script> + + +<style scoped> + .tip { + width: 0px; + height: 0px; + position: absolute; + background: transparent; + border: 10px solid #3D8FC1; + } + + .tip-up { + top: -25px; /* Same as body margin top + border */ + left: 10px; + border-right-color: transparent; + border-left-color: transparent; + border-top-color: transparent; + } + + .dialogbox .body { + position: relative; + height: auto; + margin: 20px 10px 10px 10px; + padding: 5px; + background-color: #F3F3F3; + border-radius: 5px; + border: 5px solid #3D8FC1; + } + + .body .message { + font-family: Roboto, sans-serif; + min-height: 30px; + border-radius: 3px; + font-size: 14px; + line-height: 1.5; + } +</style> diff --git a/frontend/src/components/submission_notes/FeedbackForm.vue b/frontend/src/components/submission_notes/FeedbackForm.vue new file mode 100644 index 0000000000000000000000000000000000000000..7813c0be9323b110ad7a19518de65c1f35a7fa60 --- /dev/null +++ b/frontend/src/components/submission_notes/FeedbackForm.vue @@ -0,0 +1,54 @@ +<template> + <div> + <v-text-field + name="feedback-input" + label="Please provide your feedback here" + v-model="current_feedback" + @keyup.enter.ctrl.exact="submitFeedback" + @keyup.esc="collapseTextField" + rows="2" + textarea + autofocus + auto-grow + hide-details + ></v-text-field> + <v-btn color="success" @click="submitFeedback">Submit</v-btn> + <v-btn @click="discardFeedback">Discard changes</v-btn> + </div> +</template> + + +<script> + export default { + name: 'comment-form', + props: ['feedback', 'index'], + data () { + return { + current_feedback: this.feedback + } + }, + methods: { + + collapseTextField () { + this.$emit('collapseFeedbackForm') + }, + submitFeedback () { + this.$store.dispatch('updateFeedback', { + lineIndex: this.index, + content: this.current_feedback + }) + this.collapseTextField() + }, + discardFeedback () { + this.current_feedback = this.feedback + } + } + } +</script> + + +<style scoped> + v-text-field { + padding-top: 0px; + } +</style> diff --git a/frontend/src/main.js b/frontend/src/main.js index 9a43b0eea0680622375f3caf543207d03cbc4a1e..d7cf28b7ac93b39520f3c04233df92b88fc04f8e 100644 --- a/frontend/src/main.js +++ b/frontend/src/main.js @@ -8,6 +8,8 @@ import Vuetify from 'vuetify' import 'vuetify/dist/vuetify.min.css' import 'material-design-icons/iconfont/material-icons.css' +import 'google-code-prettify/bin/prettify.min' +import 'google-code-prettify/bin/prettify.min.css' Vue.use(Vuetify) diff --git a/frontend/src/router/index.js b/frontend/src/router/index.js index 3e5f67cc59bb4ecb70ffb8ba3a9a94e3c2e4862b..74fe92f3e8b13165d140c2e24e16640066c8c063 100644 --- a/frontend/src/router/index.js +++ b/frontend/src/router/index.js @@ -6,6 +6,7 @@ import SubmissionDetail from '@/components/student/SubmissionDetail' import ReviewerPage from '@/components/reviewer/ReviewerPage' import StudentListOverview from '@/components/reviewer/StudentListOverview' import BaseLayout from '@/components/base/BaseLayout' +import AnnotatedSubmission from '@/components/submission_notes/AnnotatedSubmission' Vue.use(Router) @@ -42,6 +43,11 @@ export default new Router({ path: '/base/', name: 'base-layout', component: BaseLayout + }, + { + path: '/notes/', + name: 'annotated-submission', + component: AnnotatedSubmission } ] }) diff --git a/frontend/src/store/api.js b/frontend/src/store/api.js index 368c09c86195ded2f5c97b1199a26e37c4184190..2be0ed429deada34d26903031a564dbf31afde6a 100644 --- a/frontend/src/store/api.js +++ b/frontend/src/store/api.js @@ -1,6 +1,6 @@ import axios from 'axios' -var ax = axios.create({ +let ax = axios.create({ baseURL: 'http://localhost:8000/' }) diff --git a/frontend/src/store/modules/submission-notes.js b/frontend/src/store/modules/submission-notes.js new file mode 100644 index 0000000000000000000000000000000000000000..cde09fa0c39e2f4a38869dc8746aee0c3be86628 --- /dev/null +++ b/frontend/src/store/modules/submission-notes.js @@ -0,0 +1,76 @@ +import Vue from 'vue' + +const mockSubmission = '//Procedural Programming technique shows creation of Pascal\'s Triangl\n' + + '#include <iostream>\n' + + '#include <iomanip>\n' + + 'using namespace std;\n' + + 'int** comb(int** a , int row , int col)\n' + + '{\n' + + ' int mid = col/2;\n' + + ' //clear matrix\n' + + ' for( int i = 0 ; i < row ; i++)\n' + + ' for( int j = 0 ; j < col ; j++)\n' + + ' a[i][j] = 0;\n' + + ' a[0][mid] = 1; //put 1 in the middle of first row\n' + + ' //build up Pascal\'s Triangle matrix\n' + + ' for( int i = 1 ; i < row ; i++)\n' + + ' {\n' + + ' for( int j = 1 ; j < col - 1 ; j++)\n' + + ' a[i][j] = a[i-1][j-1] + a[i-1][j+1];\n' + + ' }\n' + + ' return a;\n' + + '}\n' + + 'void disp(int** ptr, int row, int col)\n' + + '{\n' + + ' cout << endl << endl;\n' + + ' for ( int i = 0 ; i < row ; i++)\n' + + ' {\n' + + ' for ( int j = 0 ; j < col ; j++)\n' + +const mockFeedback = { + '1': 'Youre STUPID', + '4': 'Very much so' +} + +const submissionNotes = { + state: { + rawSubmission: '', + feedback: {} + }, + getters: { + // reduce the string rawSubmission into an object where the keys are the + // line indexes starting at one and the values the corresponding submission line + // this makes iterating over the submission much more pleasant + submission: state => { + return state.rawSubmission.split('\n').reduce((acc, cur, index) => { + acc[index + 1] = cur + return acc + }, {}) + } + }, + mutations: { + 'SET_RAW_SUBMISSION': function (state, submission) { + state.rawSubmission = mockSubmission + }, + 'SET_FEEDBACK': function (state, feedback) { + state.feedback = feedback + }, + 'UPDATE_FEEDBACK': function (state, feedback) { + Vue.set(state.feedback, feedback.lineIndex, feedback.content) + } + }, + actions: { + // TODO remove mock data + getSubmission (context, submissionId) { + context.commit('SET_RAW_SUBMISSION', mockSubmission) + }, + getFeedback (context, feedbackId) { + context.commit('SET_FEEDBACK', mockFeedback) + }, + updateFeedback (context, lineIndex, feedbackContent) { + context.commit('UPDATE_FEEDBACK', lineIndex, feedbackContent) + } + } +} + +export default submissionNotes diff --git a/frontend/src/store/store.js b/frontend/src/store/store.js index 9766bf89d1f78a8004278f201912618921eba887..59525d1c5b1f1c7c85db494f10dd8d51ac536c3d 100644 --- a/frontend/src/store/store.js +++ b/frontend/src/store/store.js @@ -3,10 +3,14 @@ import Vue from 'vue' import ax from './api' import gradySays from './grady_speak' +import submissionNotes from './modules/submission-notes' Vue.use(Vuex) const store = new Vuex.Store({ + modules: { + submissionNotes: submissionNotes + }, state: { token: '', loggedIn: false, diff --git a/frontend/yarn.lock b/frontend/yarn.lock index e4ccbb2e378208009ccbf7e2e92e305fef76116a..1e43569c96644d2375e6b23a0c94ead389528e24 100644 --- a/frontend/yarn.lock +++ b/frontend/yarn.lock @@ -228,6 +228,12 @@ async@1.x, async@^1.4.0, async@^1.5.2: version "1.5.2" resolved "https://registry.yarnpkg.com/async/-/async-1.5.2.tgz#ec6a61ae56480c0c3cb241c95618e20892f9672a" +async@2.4.1: + version "2.4.1" + resolved "https://registry.yarnpkg.com/async/-/async-2.4.1.tgz#62a56b279c98a11d0987096a01cc3eeb8eb7bbd7" + dependencies: + lodash "^4.14.0" + async@^2.1.2, async@^2.4.1: version "2.6.0" resolved "https://registry.yarnpkg.com/async/-/async-2.6.0.tgz#61a29abb6fcc026fea77e56d1c6ec53a795951f4" @@ -1355,6 +1361,13 @@ component-inherit@0.0.3: version "0.0.3" resolved "https://registry.yarnpkg.com/component-inherit/-/component-inherit-0.0.3.tgz#645fc4adf58b72b649d5cae65135619db26ff143" +compression-webpack-plugin@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/compression-webpack-plugin/-/compression-webpack-plugin-1.0.1.tgz#7f0a2af9f642b4f87b5989516a3b9e9b41bb4b3f" + dependencies: + async "2.4.1" + webpack-sources "^1.0.1" + concat-map@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" @@ -2682,6 +2695,10 @@ globby@^5.0.0: pify "^2.0.0" pinkie-promise "^2.0.0" +google-code-prettify@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/google-code-prettify/-/google-code-prettify-1.0.5.tgz#9f477f224dbfa62372e5ef803a7e157410400084" + graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.1.9: version "4.1.11" resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.1.11.tgz#0e8bdfe4d1ddb8854d64e04ea7c00e2a026e5658"