├── frontend ├── .browserslistrc ├── postcss.config.js ├── babel.config.js ├── public │ ├── favicon.ico │ ├── beta_logo.png │ ├── favicons │ │ ├── favicon.ico │ │ ├── favicon-16x16.png │ │ ├── favicon-32x32.png │ │ ├── apple-touch-icon.png │ │ ├── android-chrome-192x192.png │ │ ├── android-chrome-512x512.png │ │ └── site.webmanifest │ ├── robots.txt │ └── index.html ├── src │ ├── assets │ │ ├── logo.png │ │ ├── user-photo-placeholder.png │ │ └── google_signin_buttons │ │ │ ├── ios │ │ │ ├── 1x │ │ │ │ ├── btn_google_dark_focus_ios.png │ │ │ │ ├── btn_google_dark_normal_ios.png │ │ │ │ ├── btn_google_dark_pressed_ios.png │ │ │ │ ├── btn_google_light_focus_ios.png │ │ │ │ ├── btn_google_light_normal_ios.png │ │ │ │ ├── btn_google_dark_disabled_ios.png │ │ │ │ ├── btn_google_light_disabled_ios.png │ │ │ │ ├── btn_google_light_pressed_ios.png │ │ │ │ ├── btn_google_signin_dark_focus_ios.png │ │ │ │ ├── btn_google_signin_dark_disabled_ios.png │ │ │ │ ├── btn_google_signin_dark_normal_ios.png │ │ │ │ ├── btn_google_signin_dark_pressed_ios.png │ │ │ │ ├── btn_google_signin_light_focus_ios.png │ │ │ │ ├── btn_google_signin_light_normal_ios.png │ │ │ │ ├── btn_google_signin_light_pressed_ios.png │ │ │ │ └── btn_google_signin_light_disabled_ios.png │ │ │ ├── 2x │ │ │ │ ├── btn_google_dark_focus_ios@2x.png │ │ │ │ ├── btn_google_dark_normal_ios@2x.png │ │ │ │ ├── btn_google_dark_pressed_ios@2x.png │ │ │ │ ├── btn_google_light_focus_ios@2x.png │ │ │ │ ├── btn_google_light_normal_ios@2x.png │ │ │ │ ├── btn_google_dark_disabled_ios@2x.png │ │ │ │ ├── btn_google_light_disabled_ios@2x.png │ │ │ │ ├── btn_google_light_pressed_ios@2x.png │ │ │ │ ├── btn_google_signin_dark_focus_ios@2x.png │ │ │ │ ├── btn_google_signin_dark_normal_ios@2x.png │ │ │ │ ├── btn_google_signin_dark_pressed_ios@2x.png │ │ │ │ ├── btn_google_signin_light_focus_ios@2x.png │ │ │ │ ├── btn_google_signin_light_normal_ios@2x.png │ │ │ │ ├── btn_google_signin_dark_disabled_ios@2x.png │ │ │ │ ├── btn_google_signin_light_disabled_ios@2x.png │ │ │ │ └── btn_google_signin_light_pressed_ios@2x.png │ │ │ └── 3x │ │ │ │ ├── btn_google_dark_focus_ios@3x.png │ │ │ │ ├── btn_google_dark_normal_ios@3x.png │ │ │ │ ├── btn_google_dark_pressed_ios@3x.png │ │ │ │ ├── btn_google_light_focus_ios@3x.png │ │ │ │ ├── btn_google_light_normal_ios@3x.png │ │ │ │ ├── btn_google_dark_disabled_ios@3x.png │ │ │ │ ├── btn_google_light_disabled_ios@3x.png │ │ │ │ ├── btn_google_light_pressed_ios@3x.png │ │ │ │ ├── btn_google_signin_dark_focus_ios@3x.png │ │ │ │ ├── btn_google_signin_dark_normal_ios@3x.png │ │ │ │ ├── btn_google_signin_dark_pressed_ios@3x.png │ │ │ │ ├── btn_google_signin_light_focus_ios@3x.png │ │ │ │ ├── btn_google_signin_light_normal_ios@3x.png │ │ │ │ ├── btn_google_signin_dark_disabled_ios@3x.png │ │ │ │ ├── btn_google_signin_light_disabled_ios@3x.png │ │ │ │ └── btn_google_signin_light_pressed_ios@3x.png │ │ │ ├── web │ │ │ ├── 1x │ │ │ │ ├── btn_google_signin_dark_focus_web.png │ │ │ │ ├── btn_google_signin_dark_disabled_web.png │ │ │ │ ├── btn_google_signin_dark_normal_web.png │ │ │ │ ├── btn_google_signin_dark_pressed_web.png │ │ │ │ ├── btn_google_signin_light_focus_web.png │ │ │ │ ├── btn_google_signin_light_normal_web.png │ │ │ │ ├── btn_google_signin_light_pressed_web.png │ │ │ │ └── btn_google_signin_light_disabled_web.png │ │ │ ├── 2x │ │ │ │ ├── btn_google_signin_dark_focus_web@2x.png │ │ │ │ ├── btn_google_signin_dark_normal_web@2x.png │ │ │ │ ├── btn_google_signin_dark_pressed_web@2x.png │ │ │ │ ├── btn_google_signin_light_focus_web@2x.png │ │ │ │ ├── btn_google_signin_light_normal_web@2x.png │ │ │ │ ├── btn_google_signin_dark_disabled_web@2x.png │ │ │ │ ├── btn_google_signin_light_disabled_web@2x.png │ │ │ │ └── btn_google_signin_light_pressed_web@2x.png │ │ │ └── vector │ │ │ │ ├── btn_google_dark_disabled_ios.svg │ │ │ │ ├── btn_google_light_disabled_ios.svg │ │ │ │ ├── btn_google_light_normal_ios.svg │ │ │ │ ├── btn_google_light_pressed_ios.svg │ │ │ │ ├── btn_google_light_focus_ios.svg │ │ │ │ └── btn_google_dark_normal_ios.svg │ │ │ └── android │ │ │ ├── hdpi │ │ │ ├── btn_google_dark_focus_hdpi.9.png │ │ │ ├── btn_google_dark_normal_hdpi.9.png │ │ │ ├── btn_google_light_focus_hdpi.9.png │ │ │ ├── btn_google_dark_disabled_hdpi.9.png │ │ │ ├── btn_google_dark_pressed_hdpi.9.png │ │ │ ├── btn_google_light_normal_hdpi.9.png │ │ │ ├── btn_google_light_pressed_hdpi.9.png │ │ │ ├── btn_google_light_disabled_hdpi.9.png │ │ │ ├── btn_google_signin_dark_focus_hdpi.9.png │ │ │ ├── btn_google_signin_dark_normal_hdpi.9.png │ │ │ ├── btn_google_signin_light_focus_hdpi.9.png │ │ │ ├── btn_google_signin_dark_disabled_hdpi.9.png │ │ │ ├── btn_google_signin_dark_pressed_hdpi.9.png │ │ │ ├── btn_google_signin_light_disabled_hdpi.9.png │ │ │ ├── btn_google_signin_light_normal_hdpi.9.png │ │ │ └── btn_google_signin_light_pressed_hdpi.9.png │ │ │ ├── ldpi │ │ │ ├── btn_google_dark_focus_ldpi.9.png │ │ │ ├── btn_google_dark_normal_ldpi.9.png │ │ │ ├── btn_google_light_focus_ldpi.9.png │ │ │ ├── btn_google_dark_disabled_ldpi.9.png │ │ │ ├── btn_google_dark_pressed_ldpi.9.png │ │ │ ├── btn_google_light_normal_ldpi.9.png │ │ │ ├── btn_google_light_pressed_ldpi.9.png │ │ │ ├── btn_google_light_disabled_ldpi.9.png │ │ │ ├── btn_google_signin_dark_focus_ldpi.9.png │ │ │ ├── btn_google_signin_dark_normal_ldpi.9.png │ │ │ ├── btn_google_signin_light_focus_ldpi.9.png │ │ │ ├── btn_google_signin_dark_disabled_ldpi.9.png │ │ │ ├── btn_google_signin_dark_pressed_ldpi.9.png │ │ │ ├── btn_google_signin_light_disabled_ldpi.9.png │ │ │ ├── btn_google_signin_light_normal_ldpi.9.png │ │ │ └── btn_google_signin_light_pressed_ldpi.9.png │ │ │ ├── mdpi │ │ │ ├── btn_google_dark_focus_mdpi.9.png │ │ │ ├── btn_google_dark_normal_mdpi.9.png │ │ │ ├── btn_google_light_focus_mdpi.9.png │ │ │ ├── btn_google_dark_disabled_mdpi.9.png │ │ │ ├── btn_google_dark_pressed_mdpi.9.png │ │ │ ├── btn_google_light_normal_mdpi.9.png │ │ │ ├── btn_google_light_pressed_mdpi.9.png │ │ │ ├── btn_google_light_disabled_mdpi.9.png │ │ │ ├── btn_google_signin_dark_focus_mdpi.9.png │ │ │ ├── btn_google_signin_dark_normal_mdpi.9.png │ │ │ ├── btn_google_signin_light_focus_mdpi.9.png │ │ │ ├── btn_google_signin_dark_disabled_mdpi.9.png │ │ │ ├── btn_google_signin_dark_pressed_mdpi.9.png │ │ │ ├── btn_google_signin_light_disabled_mdpi.9.png │ │ │ ├── btn_google_signin_light_normal_mdpi.9.png │ │ │ └── btn_google_signin_light_pressed_mdpi.9.png │ │ │ ├── tvdpi │ │ │ ├── btn_google_dark_focus_tvdpi.9.png │ │ │ ├── btn_google_dark_normal_tvdpi.9.png │ │ │ ├── btn_google_light_focus_tvdpi.9.png │ │ │ ├── btn_google_dark_disabled_tvdpi.9.png │ │ │ ├── btn_google_dark_pressed_tvdpi.9.png │ │ │ ├── btn_google_light_disabled_tvdpi.9.png │ │ │ ├── btn_google_light_normal_tvdpi.9.png │ │ │ ├── btn_google_light_pressed_tvdpi.9.png │ │ │ ├── btn_google_signin_dark_focus_tvdpi.9.png │ │ │ ├── btn_google_signin_dark_normal_tvdpi.9.png │ │ │ ├── btn_google_signin_dark_pressed_tvdpi.9.png │ │ │ ├── btn_google_signin_light_focus_tvdpi.9.png │ │ │ ├── btn_google_signin_light_normal_tvdpi.9.png │ │ │ ├── btn_google_signin_dark_disabled_tvdpi.9.png │ │ │ ├── btn_google_signin_light_disabled_tvdpi.9.png │ │ │ └── btn_google_signin_light_pressed_tvdpi.9.png │ │ │ ├── xhdpi │ │ │ ├── btn_google_dark_focus_xhdpi.9.png │ │ │ ├── btn_google_dark_normal_xhdpi.9.png │ │ │ ├── btn_google_light_focus_xhdpi.9.png │ │ │ ├── btn_google_dark_disabled_xhdpi.9.png │ │ │ ├── btn_google_dark_pressed_xhdpi.9.png │ │ │ ├── btn_google_light_disabled_xhdpi.9.png │ │ │ ├── btn_google_light_normal_xhdpi.9.png │ │ │ ├── btn_google_light_pressed_xhdpi.9.png │ │ │ ├── btn_google_signin_dark_focus_xhdpi.9.png │ │ │ ├── btn_google_signin_dark_normal_xhdpi.9.png │ │ │ ├── btn_google_signin_dark_pressed_xhdpi.9.png │ │ │ ├── btn_google_signin_light_focus_xhdpi.9.png │ │ │ ├── btn_google_signin_light_normal_xhdpi.9.png │ │ │ ├── btn_google_signin_dark_disabled_xhdpi.9.png │ │ │ ├── btn_google_signin_light_disabled_xhdpi.9.png │ │ │ └── btn_google_signin_light_pressed_xhdpi.9.png │ │ │ ├── xxhdpi │ │ │ ├── btn_google_dark_focus_xxhdpi.9.png │ │ │ ├── btn_google_dark_normal_xxhdpi.9.png │ │ │ ├── btn_google_dark_pressed_xxhdpi.9.png │ │ │ ├── btn_google_light_focus_xxhdpi.9.png │ │ │ ├── btn_google_light_normal_xxhdpi.9.png │ │ │ ├── btn_google_dark_disabled_xxhdpi.9.png │ │ │ ├── btn_google_light_disabled_xxhdpi.9.png │ │ │ ├── btn_google_light_pressed_xxhdpi.9.png │ │ │ ├── btn_google_signin_dark_focus_xxhdpi.9.png │ │ │ ├── btn_google_signin_dark_normal_xxhdpi.9.png │ │ │ ├── btn_google_signin_dark_pressed_xxhdpi.9.png │ │ │ ├── btn_google_signin_light_focus_xxhdpi.9.png │ │ │ ├── btn_google_signin_light_normal_xxhdpi.9.png │ │ │ ├── btn_google_signin_dark_disabled_xxhdpi.9.png │ │ │ ├── btn_google_signin_light_disabled_xxhdpi.9.png │ │ │ └── btn_google_signin_light_pressed_xxhdpi.9.png │ │ │ └── xxxhdpi │ │ │ ├── btn_google_dark_focus_xxxhdpi.9.png │ │ │ ├── btn_google_dark_normal_xxxhdpi.9.png │ │ │ ├── btn_google_dark_pressed_xxxhdpi.9.png │ │ │ ├── btn_google_light_focus_xxxhdpi.9.png │ │ │ ├── btn_google_light_normal_xxxhdpi.9.png │ │ │ ├── btn_google_dark_disabled_xxxhdpi.9.png │ │ │ ├── btn_google_light_disabled_xxxhdpi.9.png │ │ │ ├── btn_google_light_pressed_xxxhdpi.9.png │ │ │ ├── btn_google_signin_dark_focus_xxxhdpi.9.png │ │ │ ├── btn_google_signin_dark_disabled_xxxhdpi.9.png │ │ │ ├── btn_google_signin_dark_normal_xxxhdpi.9.png │ │ │ ├── btn_google_signin_dark_pressed_xxxhdpi.9.png │ │ │ ├── btn_google_signin_light_focus_xxxhdpi.9.png │ │ │ ├── btn_google_signin_light_normal_xxxhdpi.9.png │ │ │ ├── btn_google_signin_light_pressed_xxxhdpi.9.png │ │ │ └── btn_google_signin_light_disabled_xxxhdpi.9.png │ ├── plugins │ │ └── Dayjs.js │ ├── views │ │ ├── NotFound.vue │ │ └── About.vue │ ├── components │ │ ├── schedules │ │ │ ├── BuilderView.vue │ │ │ └── ScheduleView.vue │ │ ├── events │ │ │ └── ListItems │ │ │ │ ├── CustomEventListItem.vue │ │ │ │ ├── EventListItem.vue │ │ │ │ └── CourseListItem.vue │ │ └── util │ │ │ ├── color-utils.js │ │ │ └── util-methods.js │ ├── router │ │ └── index.js │ ├── store │ │ ├── getters.js │ │ └── index.js │ ├── main.js │ └── App.vue ├── tests │ ├── e2e │ │ ├── .eslintrc.js │ │ ├── specs │ │ │ ├── test.js │ │ │ ├── test-with-pageobjects.js │ │ │ └── ClassList.spec.js │ │ ├── custom-commands │ │ │ ├── openHomepageClass.js │ │ │ ├── openHomepage.js │ │ │ └── customExecute.js │ │ ├── custom-assertions │ │ │ └── elementCount.js │ │ ├── page-objects │ │ │ └── homepage.js │ │ └── globals.js │ └── unit │ │ ├── store │ │ ├── getters.spec.js │ │ └── mutations.spec.js │ │ └── components │ │ ├── CustomEventForm.spec.js │ │ └── SelectedEvents.spec.js ├── jest.config.js ├── .gitignore ├── .eslintrc.js ├── vue.config.js ├── package.json ├── README.md └── pom.xml ├── Procfile ├── .vscode ├── settings.json └── launch.json ├── backend ├── src │ ├── main │ │ ├── java │ │ │ └── org │ │ │ │ └── gaucho │ │ │ │ └── courses │ │ │ │ ├── DTO │ │ │ │ ├── ScheduleControllerResponse.java │ │ │ │ ├── ScheduleControllerRequest.java │ │ │ │ └── ScheduleControllerSaveRequest.java │ │ │ │ ├── repository │ │ │ │ └── ScheduleRepository.java │ │ │ │ ├── domain │ │ │ │ ├── scheduling │ │ │ │ │ └── CourseAndClassIds.java │ │ │ │ ├── remote │ │ │ │ │ ├── CustomEvent.java │ │ │ │ │ ├── Department.java │ │ │ │ │ ├── Quarter.java │ │ │ │ │ ├── Instructor.java │ │ │ │ │ ├── GeneralEducation.java │ │ │ │ │ ├── ClassSection.java │ │ │ │ │ └── Class.java │ │ │ │ └── core │ │ │ │ │ └── Event.java │ │ │ │ ├── config │ │ │ │ └── CustomTelemetryInitializer.java │ │ │ │ ├── GauchoCoursesApplication.java │ │ │ │ ├── auth │ │ │ │ ├── SecurityConfig.java │ │ │ │ └── UserController.java │ │ │ │ └── controller │ │ │ │ └── Gateway.java │ │ └── resources │ │ │ ├── application.properties │ │ │ ├── application-dev.properties │ │ │ ├── application-prod.properties │ │ │ └── ApplicationInsights.xml │ └── test │ │ └── java │ │ └── org │ │ └── gaucho │ │ └── courses │ │ └── GauchoCoursesApplicationTests.java └── README.md ├── README.md ├── .github └── workflows │ ├── test_pr.yml │ ├── ci.yml │ └── deploy.yml ├── Dockerfile ├── pom.xml └── .gitignore /frontend/.browserslistrc: -------------------------------------------------------------------------------- 1 | > 1% 2 | last 2 versions 3 | -------------------------------------------------------------------------------- /Procfile: -------------------------------------------------------------------------------- 1 | web: java $JAVA_OPTS -Dserver.port=$PORT -jar target/*.jar -------------------------------------------------------------------------------- /frontend/postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | autoprefixer: {} 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /frontend/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | '@vue/cli-plugin-babel/preset' 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /frontend/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/data-science-ucsb/gauchocourses/HEAD/frontend/public/favicon.ico -------------------------------------------------------------------------------- /frontend/src/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/data-science-ucsb/gauchocourses/HEAD/frontend/src/assets/logo.png -------------------------------------------------------------------------------- /frontend/tests/e2e/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | rules: { 3 | 'no-unused-expressions': 'off' 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /frontend/public/beta_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/data-science-ucsb/gauchocourses/HEAD/frontend/public/beta_logo.png -------------------------------------------------------------------------------- /frontend/public/favicons/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/data-science-ucsb/gauchocourses/HEAD/frontend/public/favicons/favicon.ico -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "java.configuration.updateBuildConfiguration": "automatic", 3 | "java.compile.nullAnalysis.mode": "automatic" 4 | } -------------------------------------------------------------------------------- /frontend/public/favicons/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/data-science-ucsb/gauchocourses/HEAD/frontend/public/favicons/favicon-16x16.png -------------------------------------------------------------------------------- /frontend/public/favicons/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/data-science-ucsb/gauchocourses/HEAD/frontend/public/favicons/favicon-32x32.png -------------------------------------------------------------------------------- /frontend/public/favicons/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/data-science-ucsb/gauchocourses/HEAD/frontend/public/favicons/apple-touch-icon.png -------------------------------------------------------------------------------- /frontend/src/assets/user-photo-placeholder.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/data-science-ucsb/gauchocourses/HEAD/frontend/src/assets/user-photo-placeholder.png -------------------------------------------------------------------------------- /frontend/public/favicons/android-chrome-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/data-science-ucsb/gauchocourses/HEAD/frontend/public/favicons/android-chrome-192x192.png -------------------------------------------------------------------------------- /frontend/public/favicons/android-chrome-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/data-science-ucsb/gauchocourses/HEAD/frontend/public/favicons/android-chrome-512x512.png -------------------------------------------------------------------------------- /frontend/src/assets/google_signin_buttons/ios/1x/btn_google_dark_focus_ios.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/data-science-ucsb/gauchocourses/HEAD/frontend/src/assets/google_signin_buttons/ios/1x/btn_google_dark_focus_ios.png -------------------------------------------------------------------------------- /frontend/src/assets/google_signin_buttons/ios/1x/btn_google_dark_normal_ios.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/data-science-ucsb/gauchocourses/HEAD/frontend/src/assets/google_signin_buttons/ios/1x/btn_google_dark_normal_ios.png -------------------------------------------------------------------------------- /frontend/src/assets/google_signin_buttons/ios/1x/btn_google_dark_pressed_ios.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/data-science-ucsb/gauchocourses/HEAD/frontend/src/assets/google_signin_buttons/ios/1x/btn_google_dark_pressed_ios.png -------------------------------------------------------------------------------- /frontend/src/assets/google_signin_buttons/ios/1x/btn_google_light_focus_ios.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/data-science-ucsb/gauchocourses/HEAD/frontend/src/assets/google_signin_buttons/ios/1x/btn_google_light_focus_ios.png -------------------------------------------------------------------------------- /frontend/src/assets/google_signin_buttons/ios/1x/btn_google_light_normal_ios.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/data-science-ucsb/gauchocourses/HEAD/frontend/src/assets/google_signin_buttons/ios/1x/btn_google_light_normal_ios.png -------------------------------------------------------------------------------- /frontend/src/assets/google_signin_buttons/ios/1x/btn_google_dark_disabled_ios.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/data-science-ucsb/gauchocourses/HEAD/frontend/src/assets/google_signin_buttons/ios/1x/btn_google_dark_disabled_ios.png -------------------------------------------------------------------------------- /frontend/src/assets/google_signin_buttons/ios/1x/btn_google_light_disabled_ios.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/data-science-ucsb/gauchocourses/HEAD/frontend/src/assets/google_signin_buttons/ios/1x/btn_google_light_disabled_ios.png -------------------------------------------------------------------------------- /frontend/src/assets/google_signin_buttons/ios/1x/btn_google_light_pressed_ios.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/data-science-ucsb/gauchocourses/HEAD/frontend/src/assets/google_signin_buttons/ios/1x/btn_google_light_pressed_ios.png -------------------------------------------------------------------------------- /frontend/src/assets/google_signin_buttons/ios/2x/btn_google_dark_focus_ios@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/data-science-ucsb/gauchocourses/HEAD/frontend/src/assets/google_signin_buttons/ios/2x/btn_google_dark_focus_ios@2x.png -------------------------------------------------------------------------------- /frontend/src/assets/google_signin_buttons/ios/2x/btn_google_dark_normal_ios@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/data-science-ucsb/gauchocourses/HEAD/frontend/src/assets/google_signin_buttons/ios/2x/btn_google_dark_normal_ios@2x.png -------------------------------------------------------------------------------- /frontend/src/assets/google_signin_buttons/ios/2x/btn_google_dark_pressed_ios@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/data-science-ucsb/gauchocourses/HEAD/frontend/src/assets/google_signin_buttons/ios/2x/btn_google_dark_pressed_ios@2x.png -------------------------------------------------------------------------------- /frontend/src/assets/google_signin_buttons/ios/2x/btn_google_light_focus_ios@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/data-science-ucsb/gauchocourses/HEAD/frontend/src/assets/google_signin_buttons/ios/2x/btn_google_light_focus_ios@2x.png -------------------------------------------------------------------------------- /frontend/src/assets/google_signin_buttons/ios/2x/btn_google_light_normal_ios@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/data-science-ucsb/gauchocourses/HEAD/frontend/src/assets/google_signin_buttons/ios/2x/btn_google_light_normal_ios@2x.png -------------------------------------------------------------------------------- /frontend/src/assets/google_signin_buttons/ios/3x/btn_google_dark_focus_ios@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/data-science-ucsb/gauchocourses/HEAD/frontend/src/assets/google_signin_buttons/ios/3x/btn_google_dark_focus_ios@3x.png -------------------------------------------------------------------------------- /frontend/src/assets/google_signin_buttons/ios/3x/btn_google_dark_normal_ios@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/data-science-ucsb/gauchocourses/HEAD/frontend/src/assets/google_signin_buttons/ios/3x/btn_google_dark_normal_ios@3x.png -------------------------------------------------------------------------------- /frontend/src/assets/google_signin_buttons/ios/3x/btn_google_dark_pressed_ios@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/data-science-ucsb/gauchocourses/HEAD/frontend/src/assets/google_signin_buttons/ios/3x/btn_google_dark_pressed_ios@3x.png -------------------------------------------------------------------------------- /frontend/src/assets/google_signin_buttons/ios/3x/btn_google_light_focus_ios@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/data-science-ucsb/gauchocourses/HEAD/frontend/src/assets/google_signin_buttons/ios/3x/btn_google_light_focus_ios@3x.png -------------------------------------------------------------------------------- /frontend/src/assets/google_signin_buttons/ios/3x/btn_google_light_normal_ios@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/data-science-ucsb/gauchocourses/HEAD/frontend/src/assets/google_signin_buttons/ios/3x/btn_google_light_normal_ios@3x.png -------------------------------------------------------------------------------- /frontend/src/assets/google_signin_buttons/ios/1x/btn_google_signin_dark_focus_ios.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/data-science-ucsb/gauchocourses/HEAD/frontend/src/assets/google_signin_buttons/ios/1x/btn_google_signin_dark_focus_ios.png -------------------------------------------------------------------------------- /frontend/src/assets/google_signin_buttons/ios/2x/btn_google_dark_disabled_ios@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/data-science-ucsb/gauchocourses/HEAD/frontend/src/assets/google_signin_buttons/ios/2x/btn_google_dark_disabled_ios@2x.png -------------------------------------------------------------------------------- /frontend/src/assets/google_signin_buttons/ios/2x/btn_google_light_disabled_ios@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/data-science-ucsb/gauchocourses/HEAD/frontend/src/assets/google_signin_buttons/ios/2x/btn_google_light_disabled_ios@2x.png -------------------------------------------------------------------------------- /frontend/src/assets/google_signin_buttons/ios/2x/btn_google_light_pressed_ios@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/data-science-ucsb/gauchocourses/HEAD/frontend/src/assets/google_signin_buttons/ios/2x/btn_google_light_pressed_ios@2x.png -------------------------------------------------------------------------------- /frontend/src/assets/google_signin_buttons/ios/3x/btn_google_dark_disabled_ios@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/data-science-ucsb/gauchocourses/HEAD/frontend/src/assets/google_signin_buttons/ios/3x/btn_google_dark_disabled_ios@3x.png -------------------------------------------------------------------------------- /frontend/src/assets/google_signin_buttons/ios/3x/btn_google_light_disabled_ios@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/data-science-ucsb/gauchocourses/HEAD/frontend/src/assets/google_signin_buttons/ios/3x/btn_google_light_disabled_ios@3x.png -------------------------------------------------------------------------------- /frontend/src/assets/google_signin_buttons/ios/3x/btn_google_light_pressed_ios@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/data-science-ucsb/gauchocourses/HEAD/frontend/src/assets/google_signin_buttons/ios/3x/btn_google_light_pressed_ios@3x.png -------------------------------------------------------------------------------- /frontend/src/assets/google_signin_buttons/web/1x/btn_google_signin_dark_focus_web.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/data-science-ucsb/gauchocourses/HEAD/frontend/src/assets/google_signin_buttons/web/1x/btn_google_signin_dark_focus_web.png -------------------------------------------------------------------------------- /frontend/src/assets/google_signin_buttons/android/hdpi/btn_google_dark_focus_hdpi.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/data-science-ucsb/gauchocourses/HEAD/frontend/src/assets/google_signin_buttons/android/hdpi/btn_google_dark_focus_hdpi.9.png -------------------------------------------------------------------------------- /frontend/src/assets/google_signin_buttons/android/hdpi/btn_google_dark_normal_hdpi.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/data-science-ucsb/gauchocourses/HEAD/frontend/src/assets/google_signin_buttons/android/hdpi/btn_google_dark_normal_hdpi.9.png -------------------------------------------------------------------------------- /frontend/src/assets/google_signin_buttons/android/hdpi/btn_google_light_focus_hdpi.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/data-science-ucsb/gauchocourses/HEAD/frontend/src/assets/google_signin_buttons/android/hdpi/btn_google_light_focus_hdpi.9.png -------------------------------------------------------------------------------- /frontend/src/assets/google_signin_buttons/android/ldpi/btn_google_dark_focus_ldpi.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/data-science-ucsb/gauchocourses/HEAD/frontend/src/assets/google_signin_buttons/android/ldpi/btn_google_dark_focus_ldpi.9.png -------------------------------------------------------------------------------- /frontend/src/assets/google_signin_buttons/android/ldpi/btn_google_dark_normal_ldpi.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/data-science-ucsb/gauchocourses/HEAD/frontend/src/assets/google_signin_buttons/android/ldpi/btn_google_dark_normal_ldpi.9.png -------------------------------------------------------------------------------- /frontend/src/assets/google_signin_buttons/android/ldpi/btn_google_light_focus_ldpi.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/data-science-ucsb/gauchocourses/HEAD/frontend/src/assets/google_signin_buttons/android/ldpi/btn_google_light_focus_ldpi.9.png -------------------------------------------------------------------------------- /frontend/src/assets/google_signin_buttons/android/mdpi/btn_google_dark_focus_mdpi.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/data-science-ucsb/gauchocourses/HEAD/frontend/src/assets/google_signin_buttons/android/mdpi/btn_google_dark_focus_mdpi.9.png -------------------------------------------------------------------------------- /frontend/src/assets/google_signin_buttons/android/mdpi/btn_google_dark_normal_mdpi.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/data-science-ucsb/gauchocourses/HEAD/frontend/src/assets/google_signin_buttons/android/mdpi/btn_google_dark_normal_mdpi.9.png -------------------------------------------------------------------------------- /frontend/src/assets/google_signin_buttons/android/mdpi/btn_google_light_focus_mdpi.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/data-science-ucsb/gauchocourses/HEAD/frontend/src/assets/google_signin_buttons/android/mdpi/btn_google_light_focus_mdpi.9.png -------------------------------------------------------------------------------- /frontend/src/assets/google_signin_buttons/ios/1x/btn_google_signin_dark_disabled_ios.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/data-science-ucsb/gauchocourses/HEAD/frontend/src/assets/google_signin_buttons/ios/1x/btn_google_signin_dark_disabled_ios.png -------------------------------------------------------------------------------- /frontend/src/assets/google_signin_buttons/ios/1x/btn_google_signin_dark_normal_ios.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/data-science-ucsb/gauchocourses/HEAD/frontend/src/assets/google_signin_buttons/ios/1x/btn_google_signin_dark_normal_ios.png -------------------------------------------------------------------------------- /frontend/src/assets/google_signin_buttons/ios/1x/btn_google_signin_dark_pressed_ios.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/data-science-ucsb/gauchocourses/HEAD/frontend/src/assets/google_signin_buttons/ios/1x/btn_google_signin_dark_pressed_ios.png -------------------------------------------------------------------------------- /frontend/src/assets/google_signin_buttons/ios/1x/btn_google_signin_light_focus_ios.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/data-science-ucsb/gauchocourses/HEAD/frontend/src/assets/google_signin_buttons/ios/1x/btn_google_signin_light_focus_ios.png -------------------------------------------------------------------------------- /frontend/src/assets/google_signin_buttons/ios/1x/btn_google_signin_light_normal_ios.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/data-science-ucsb/gauchocourses/HEAD/frontend/src/assets/google_signin_buttons/ios/1x/btn_google_signin_light_normal_ios.png -------------------------------------------------------------------------------- /frontend/src/assets/google_signin_buttons/ios/1x/btn_google_signin_light_pressed_ios.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/data-science-ucsb/gauchocourses/HEAD/frontend/src/assets/google_signin_buttons/ios/1x/btn_google_signin_light_pressed_ios.png -------------------------------------------------------------------------------- /frontend/src/assets/google_signin_buttons/ios/2x/btn_google_signin_dark_focus_ios@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/data-science-ucsb/gauchocourses/HEAD/frontend/src/assets/google_signin_buttons/ios/2x/btn_google_signin_dark_focus_ios@2x.png -------------------------------------------------------------------------------- /frontend/src/assets/google_signin_buttons/ios/3x/btn_google_signin_dark_focus_ios@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/data-science-ucsb/gauchocourses/HEAD/frontend/src/assets/google_signin_buttons/ios/3x/btn_google_signin_dark_focus_ios@3x.png -------------------------------------------------------------------------------- /frontend/src/assets/google_signin_buttons/web/1x/btn_google_signin_dark_disabled_web.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/data-science-ucsb/gauchocourses/HEAD/frontend/src/assets/google_signin_buttons/web/1x/btn_google_signin_dark_disabled_web.png -------------------------------------------------------------------------------- /frontend/src/assets/google_signin_buttons/web/1x/btn_google_signin_dark_normal_web.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/data-science-ucsb/gauchocourses/HEAD/frontend/src/assets/google_signin_buttons/web/1x/btn_google_signin_dark_normal_web.png -------------------------------------------------------------------------------- /frontend/src/assets/google_signin_buttons/web/1x/btn_google_signin_dark_pressed_web.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/data-science-ucsb/gauchocourses/HEAD/frontend/src/assets/google_signin_buttons/web/1x/btn_google_signin_dark_pressed_web.png -------------------------------------------------------------------------------- /frontend/src/assets/google_signin_buttons/web/1x/btn_google_signin_light_focus_web.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/data-science-ucsb/gauchocourses/HEAD/frontend/src/assets/google_signin_buttons/web/1x/btn_google_signin_light_focus_web.png -------------------------------------------------------------------------------- /frontend/src/assets/google_signin_buttons/web/1x/btn_google_signin_light_normal_web.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/data-science-ucsb/gauchocourses/HEAD/frontend/src/assets/google_signin_buttons/web/1x/btn_google_signin_light_normal_web.png -------------------------------------------------------------------------------- /frontend/src/assets/google_signin_buttons/web/1x/btn_google_signin_light_pressed_web.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/data-science-ucsb/gauchocourses/HEAD/frontend/src/assets/google_signin_buttons/web/1x/btn_google_signin_light_pressed_web.png -------------------------------------------------------------------------------- /frontend/src/assets/google_signin_buttons/web/2x/btn_google_signin_dark_focus_web@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/data-science-ucsb/gauchocourses/HEAD/frontend/src/assets/google_signin_buttons/web/2x/btn_google_signin_dark_focus_web@2x.png -------------------------------------------------------------------------------- /frontend/src/assets/google_signin_buttons/android/hdpi/btn_google_dark_disabled_hdpi.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/data-science-ucsb/gauchocourses/HEAD/frontend/src/assets/google_signin_buttons/android/hdpi/btn_google_dark_disabled_hdpi.9.png -------------------------------------------------------------------------------- /frontend/src/assets/google_signin_buttons/android/hdpi/btn_google_dark_pressed_hdpi.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/data-science-ucsb/gauchocourses/HEAD/frontend/src/assets/google_signin_buttons/android/hdpi/btn_google_dark_pressed_hdpi.9.png -------------------------------------------------------------------------------- /frontend/src/assets/google_signin_buttons/android/hdpi/btn_google_light_normal_hdpi.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/data-science-ucsb/gauchocourses/HEAD/frontend/src/assets/google_signin_buttons/android/hdpi/btn_google_light_normal_hdpi.9.png -------------------------------------------------------------------------------- /frontend/src/assets/google_signin_buttons/android/hdpi/btn_google_light_pressed_hdpi.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/data-science-ucsb/gauchocourses/HEAD/frontend/src/assets/google_signin_buttons/android/hdpi/btn_google_light_pressed_hdpi.9.png -------------------------------------------------------------------------------- /frontend/src/assets/google_signin_buttons/android/ldpi/btn_google_dark_disabled_ldpi.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/data-science-ucsb/gauchocourses/HEAD/frontend/src/assets/google_signin_buttons/android/ldpi/btn_google_dark_disabled_ldpi.9.png -------------------------------------------------------------------------------- /frontend/src/assets/google_signin_buttons/android/ldpi/btn_google_dark_pressed_ldpi.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/data-science-ucsb/gauchocourses/HEAD/frontend/src/assets/google_signin_buttons/android/ldpi/btn_google_dark_pressed_ldpi.9.png -------------------------------------------------------------------------------- /frontend/src/assets/google_signin_buttons/android/ldpi/btn_google_light_normal_ldpi.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/data-science-ucsb/gauchocourses/HEAD/frontend/src/assets/google_signin_buttons/android/ldpi/btn_google_light_normal_ldpi.9.png -------------------------------------------------------------------------------- /frontend/src/assets/google_signin_buttons/android/ldpi/btn_google_light_pressed_ldpi.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/data-science-ucsb/gauchocourses/HEAD/frontend/src/assets/google_signin_buttons/android/ldpi/btn_google_light_pressed_ldpi.9.png -------------------------------------------------------------------------------- /frontend/src/assets/google_signin_buttons/android/mdpi/btn_google_dark_disabled_mdpi.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/data-science-ucsb/gauchocourses/HEAD/frontend/src/assets/google_signin_buttons/android/mdpi/btn_google_dark_disabled_mdpi.9.png -------------------------------------------------------------------------------- /frontend/src/assets/google_signin_buttons/android/mdpi/btn_google_dark_pressed_mdpi.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/data-science-ucsb/gauchocourses/HEAD/frontend/src/assets/google_signin_buttons/android/mdpi/btn_google_dark_pressed_mdpi.9.png -------------------------------------------------------------------------------- /frontend/src/assets/google_signin_buttons/android/mdpi/btn_google_light_normal_mdpi.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/data-science-ucsb/gauchocourses/HEAD/frontend/src/assets/google_signin_buttons/android/mdpi/btn_google_light_normal_mdpi.9.png -------------------------------------------------------------------------------- /frontend/src/assets/google_signin_buttons/android/mdpi/btn_google_light_pressed_mdpi.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/data-science-ucsb/gauchocourses/HEAD/frontend/src/assets/google_signin_buttons/android/mdpi/btn_google_light_pressed_mdpi.9.png -------------------------------------------------------------------------------- /frontend/src/assets/google_signin_buttons/android/tvdpi/btn_google_dark_focus_tvdpi.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/data-science-ucsb/gauchocourses/HEAD/frontend/src/assets/google_signin_buttons/android/tvdpi/btn_google_dark_focus_tvdpi.9.png -------------------------------------------------------------------------------- /frontend/src/assets/google_signin_buttons/android/tvdpi/btn_google_dark_normal_tvdpi.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/data-science-ucsb/gauchocourses/HEAD/frontend/src/assets/google_signin_buttons/android/tvdpi/btn_google_dark_normal_tvdpi.9.png -------------------------------------------------------------------------------- /frontend/src/assets/google_signin_buttons/android/tvdpi/btn_google_light_focus_tvdpi.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/data-science-ucsb/gauchocourses/HEAD/frontend/src/assets/google_signin_buttons/android/tvdpi/btn_google_light_focus_tvdpi.9.png -------------------------------------------------------------------------------- /frontend/src/assets/google_signin_buttons/android/xhdpi/btn_google_dark_focus_xhdpi.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/data-science-ucsb/gauchocourses/HEAD/frontend/src/assets/google_signin_buttons/android/xhdpi/btn_google_dark_focus_xhdpi.9.png -------------------------------------------------------------------------------- /frontend/src/assets/google_signin_buttons/android/xhdpi/btn_google_dark_normal_xhdpi.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/data-science-ucsb/gauchocourses/HEAD/frontend/src/assets/google_signin_buttons/android/xhdpi/btn_google_dark_normal_xhdpi.9.png -------------------------------------------------------------------------------- /frontend/src/assets/google_signin_buttons/android/xhdpi/btn_google_light_focus_xhdpi.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/data-science-ucsb/gauchocourses/HEAD/frontend/src/assets/google_signin_buttons/android/xhdpi/btn_google_light_focus_xhdpi.9.png -------------------------------------------------------------------------------- /frontend/src/assets/google_signin_buttons/ios/1x/btn_google_signin_light_disabled_ios.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/data-science-ucsb/gauchocourses/HEAD/frontend/src/assets/google_signin_buttons/ios/1x/btn_google_signin_light_disabled_ios.png -------------------------------------------------------------------------------- /frontend/src/assets/google_signin_buttons/ios/2x/btn_google_signin_dark_normal_ios@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/data-science-ucsb/gauchocourses/HEAD/frontend/src/assets/google_signin_buttons/ios/2x/btn_google_signin_dark_normal_ios@2x.png -------------------------------------------------------------------------------- /frontend/src/assets/google_signin_buttons/ios/2x/btn_google_signin_dark_pressed_ios@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/data-science-ucsb/gauchocourses/HEAD/frontend/src/assets/google_signin_buttons/ios/2x/btn_google_signin_dark_pressed_ios@2x.png -------------------------------------------------------------------------------- /frontend/src/assets/google_signin_buttons/ios/2x/btn_google_signin_light_focus_ios@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/data-science-ucsb/gauchocourses/HEAD/frontend/src/assets/google_signin_buttons/ios/2x/btn_google_signin_light_focus_ios@2x.png -------------------------------------------------------------------------------- /frontend/src/assets/google_signin_buttons/ios/2x/btn_google_signin_light_normal_ios@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/data-science-ucsb/gauchocourses/HEAD/frontend/src/assets/google_signin_buttons/ios/2x/btn_google_signin_light_normal_ios@2x.png -------------------------------------------------------------------------------- /frontend/src/assets/google_signin_buttons/ios/3x/btn_google_signin_dark_normal_ios@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/data-science-ucsb/gauchocourses/HEAD/frontend/src/assets/google_signin_buttons/ios/3x/btn_google_signin_dark_normal_ios@3x.png -------------------------------------------------------------------------------- /frontend/src/assets/google_signin_buttons/ios/3x/btn_google_signin_dark_pressed_ios@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/data-science-ucsb/gauchocourses/HEAD/frontend/src/assets/google_signin_buttons/ios/3x/btn_google_signin_dark_pressed_ios@3x.png -------------------------------------------------------------------------------- /frontend/src/assets/google_signin_buttons/ios/3x/btn_google_signin_light_focus_ios@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/data-science-ucsb/gauchocourses/HEAD/frontend/src/assets/google_signin_buttons/ios/3x/btn_google_signin_light_focus_ios@3x.png -------------------------------------------------------------------------------- /frontend/src/assets/google_signin_buttons/ios/3x/btn_google_signin_light_normal_ios@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/data-science-ucsb/gauchocourses/HEAD/frontend/src/assets/google_signin_buttons/ios/3x/btn_google_signin_light_normal_ios@3x.png -------------------------------------------------------------------------------- /frontend/src/assets/google_signin_buttons/web/1x/btn_google_signin_light_disabled_web.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/data-science-ucsb/gauchocourses/HEAD/frontend/src/assets/google_signin_buttons/web/1x/btn_google_signin_light_disabled_web.png -------------------------------------------------------------------------------- /frontend/src/assets/google_signin_buttons/web/2x/btn_google_signin_dark_normal_web@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/data-science-ucsb/gauchocourses/HEAD/frontend/src/assets/google_signin_buttons/web/2x/btn_google_signin_dark_normal_web@2x.png -------------------------------------------------------------------------------- /frontend/src/assets/google_signin_buttons/web/2x/btn_google_signin_dark_pressed_web@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/data-science-ucsb/gauchocourses/HEAD/frontend/src/assets/google_signin_buttons/web/2x/btn_google_signin_dark_pressed_web@2x.png -------------------------------------------------------------------------------- /frontend/src/assets/google_signin_buttons/web/2x/btn_google_signin_light_focus_web@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/data-science-ucsb/gauchocourses/HEAD/frontend/src/assets/google_signin_buttons/web/2x/btn_google_signin_light_focus_web@2x.png -------------------------------------------------------------------------------- /frontend/src/assets/google_signin_buttons/web/2x/btn_google_signin_light_normal_web@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/data-science-ucsb/gauchocourses/HEAD/frontend/src/assets/google_signin_buttons/web/2x/btn_google_signin_light_normal_web@2x.png -------------------------------------------------------------------------------- /frontend/src/assets/google_signin_buttons/android/hdpi/btn_google_light_disabled_hdpi.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/data-science-ucsb/gauchocourses/HEAD/frontend/src/assets/google_signin_buttons/android/hdpi/btn_google_light_disabled_hdpi.9.png -------------------------------------------------------------------------------- /frontend/src/assets/google_signin_buttons/android/ldpi/btn_google_light_disabled_ldpi.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/data-science-ucsb/gauchocourses/HEAD/frontend/src/assets/google_signin_buttons/android/ldpi/btn_google_light_disabled_ldpi.9.png -------------------------------------------------------------------------------- /frontend/src/assets/google_signin_buttons/android/mdpi/btn_google_light_disabled_mdpi.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/data-science-ucsb/gauchocourses/HEAD/frontend/src/assets/google_signin_buttons/android/mdpi/btn_google_light_disabled_mdpi.9.png -------------------------------------------------------------------------------- /frontend/src/assets/google_signin_buttons/android/tvdpi/btn_google_dark_disabled_tvdpi.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/data-science-ucsb/gauchocourses/HEAD/frontend/src/assets/google_signin_buttons/android/tvdpi/btn_google_dark_disabled_tvdpi.9.png -------------------------------------------------------------------------------- /frontend/src/assets/google_signin_buttons/android/tvdpi/btn_google_dark_pressed_tvdpi.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/data-science-ucsb/gauchocourses/HEAD/frontend/src/assets/google_signin_buttons/android/tvdpi/btn_google_dark_pressed_tvdpi.9.png -------------------------------------------------------------------------------- /frontend/src/assets/google_signin_buttons/android/tvdpi/btn_google_light_disabled_tvdpi.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/data-science-ucsb/gauchocourses/HEAD/frontend/src/assets/google_signin_buttons/android/tvdpi/btn_google_light_disabled_tvdpi.9.png -------------------------------------------------------------------------------- /frontend/src/assets/google_signin_buttons/android/tvdpi/btn_google_light_normal_tvdpi.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/data-science-ucsb/gauchocourses/HEAD/frontend/src/assets/google_signin_buttons/android/tvdpi/btn_google_light_normal_tvdpi.9.png -------------------------------------------------------------------------------- /frontend/src/assets/google_signin_buttons/android/tvdpi/btn_google_light_pressed_tvdpi.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/data-science-ucsb/gauchocourses/HEAD/frontend/src/assets/google_signin_buttons/android/tvdpi/btn_google_light_pressed_tvdpi.9.png -------------------------------------------------------------------------------- /frontend/src/assets/google_signin_buttons/android/xhdpi/btn_google_dark_disabled_xhdpi.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/data-science-ucsb/gauchocourses/HEAD/frontend/src/assets/google_signin_buttons/android/xhdpi/btn_google_dark_disabled_xhdpi.9.png -------------------------------------------------------------------------------- /frontend/src/assets/google_signin_buttons/android/xhdpi/btn_google_dark_pressed_xhdpi.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/data-science-ucsb/gauchocourses/HEAD/frontend/src/assets/google_signin_buttons/android/xhdpi/btn_google_dark_pressed_xhdpi.9.png -------------------------------------------------------------------------------- /frontend/src/assets/google_signin_buttons/android/xhdpi/btn_google_light_disabled_xhdpi.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/data-science-ucsb/gauchocourses/HEAD/frontend/src/assets/google_signin_buttons/android/xhdpi/btn_google_light_disabled_xhdpi.9.png -------------------------------------------------------------------------------- /frontend/src/assets/google_signin_buttons/android/xhdpi/btn_google_light_normal_xhdpi.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/data-science-ucsb/gauchocourses/HEAD/frontend/src/assets/google_signin_buttons/android/xhdpi/btn_google_light_normal_xhdpi.9.png -------------------------------------------------------------------------------- /frontend/src/assets/google_signin_buttons/android/xhdpi/btn_google_light_pressed_xhdpi.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/data-science-ucsb/gauchocourses/HEAD/frontend/src/assets/google_signin_buttons/android/xhdpi/btn_google_light_pressed_xhdpi.9.png -------------------------------------------------------------------------------- /frontend/src/assets/google_signin_buttons/android/xxhdpi/btn_google_dark_focus_xxhdpi.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/data-science-ucsb/gauchocourses/HEAD/frontend/src/assets/google_signin_buttons/android/xxhdpi/btn_google_dark_focus_xxhdpi.9.png -------------------------------------------------------------------------------- /frontend/src/assets/google_signin_buttons/android/xxhdpi/btn_google_dark_normal_xxhdpi.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/data-science-ucsb/gauchocourses/HEAD/frontend/src/assets/google_signin_buttons/android/xxhdpi/btn_google_dark_normal_xxhdpi.9.png -------------------------------------------------------------------------------- /frontend/src/assets/google_signin_buttons/android/xxhdpi/btn_google_dark_pressed_xxhdpi.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/data-science-ucsb/gauchocourses/HEAD/frontend/src/assets/google_signin_buttons/android/xxhdpi/btn_google_dark_pressed_xxhdpi.9.png -------------------------------------------------------------------------------- /frontend/src/assets/google_signin_buttons/android/xxhdpi/btn_google_light_focus_xxhdpi.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/data-science-ucsb/gauchocourses/HEAD/frontend/src/assets/google_signin_buttons/android/xxhdpi/btn_google_light_focus_xxhdpi.9.png -------------------------------------------------------------------------------- /frontend/src/assets/google_signin_buttons/android/xxhdpi/btn_google_light_normal_xxhdpi.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/data-science-ucsb/gauchocourses/HEAD/frontend/src/assets/google_signin_buttons/android/xxhdpi/btn_google_light_normal_xxhdpi.9.png -------------------------------------------------------------------------------- /frontend/src/assets/google_signin_buttons/android/xxxhdpi/btn_google_dark_focus_xxxhdpi.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/data-science-ucsb/gauchocourses/HEAD/frontend/src/assets/google_signin_buttons/android/xxxhdpi/btn_google_dark_focus_xxxhdpi.9.png -------------------------------------------------------------------------------- /frontend/src/assets/google_signin_buttons/ios/2x/btn_google_signin_dark_disabled_ios@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/data-science-ucsb/gauchocourses/HEAD/frontend/src/assets/google_signin_buttons/ios/2x/btn_google_signin_dark_disabled_ios@2x.png -------------------------------------------------------------------------------- /frontend/src/assets/google_signin_buttons/ios/2x/btn_google_signin_light_disabled_ios@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/data-science-ucsb/gauchocourses/HEAD/frontend/src/assets/google_signin_buttons/ios/2x/btn_google_signin_light_disabled_ios@2x.png -------------------------------------------------------------------------------- /frontend/src/assets/google_signin_buttons/ios/2x/btn_google_signin_light_pressed_ios@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/data-science-ucsb/gauchocourses/HEAD/frontend/src/assets/google_signin_buttons/ios/2x/btn_google_signin_light_pressed_ios@2x.png -------------------------------------------------------------------------------- /frontend/src/assets/google_signin_buttons/ios/3x/btn_google_signin_dark_disabled_ios@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/data-science-ucsb/gauchocourses/HEAD/frontend/src/assets/google_signin_buttons/ios/3x/btn_google_signin_dark_disabled_ios@3x.png -------------------------------------------------------------------------------- /frontend/src/assets/google_signin_buttons/ios/3x/btn_google_signin_light_disabled_ios@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/data-science-ucsb/gauchocourses/HEAD/frontend/src/assets/google_signin_buttons/ios/3x/btn_google_signin_light_disabled_ios@3x.png -------------------------------------------------------------------------------- /frontend/src/assets/google_signin_buttons/ios/3x/btn_google_signin_light_pressed_ios@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/data-science-ucsb/gauchocourses/HEAD/frontend/src/assets/google_signin_buttons/ios/3x/btn_google_signin_light_pressed_ios@3x.png -------------------------------------------------------------------------------- /frontend/src/assets/google_signin_buttons/web/2x/btn_google_signin_dark_disabled_web@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/data-science-ucsb/gauchocourses/HEAD/frontend/src/assets/google_signin_buttons/web/2x/btn_google_signin_dark_disabled_web@2x.png -------------------------------------------------------------------------------- /frontend/src/assets/google_signin_buttons/web/2x/btn_google_signin_light_disabled_web@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/data-science-ucsb/gauchocourses/HEAD/frontend/src/assets/google_signin_buttons/web/2x/btn_google_signin_light_disabled_web@2x.png -------------------------------------------------------------------------------- /frontend/src/assets/google_signin_buttons/web/2x/btn_google_signin_light_pressed_web@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/data-science-ucsb/gauchocourses/HEAD/frontend/src/assets/google_signin_buttons/web/2x/btn_google_signin_light_pressed_web@2x.png -------------------------------------------------------------------------------- /frontend/src/assets/google_signin_buttons/android/hdpi/btn_google_signin_dark_focus_hdpi.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/data-science-ucsb/gauchocourses/HEAD/frontend/src/assets/google_signin_buttons/android/hdpi/btn_google_signin_dark_focus_hdpi.9.png -------------------------------------------------------------------------------- /frontend/src/assets/google_signin_buttons/android/hdpi/btn_google_signin_dark_normal_hdpi.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/data-science-ucsb/gauchocourses/HEAD/frontend/src/assets/google_signin_buttons/android/hdpi/btn_google_signin_dark_normal_hdpi.9.png -------------------------------------------------------------------------------- /frontend/src/assets/google_signin_buttons/android/hdpi/btn_google_signin_light_focus_hdpi.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/data-science-ucsb/gauchocourses/HEAD/frontend/src/assets/google_signin_buttons/android/hdpi/btn_google_signin_light_focus_hdpi.9.png -------------------------------------------------------------------------------- /frontend/src/assets/google_signin_buttons/android/ldpi/btn_google_signin_dark_focus_ldpi.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/data-science-ucsb/gauchocourses/HEAD/frontend/src/assets/google_signin_buttons/android/ldpi/btn_google_signin_dark_focus_ldpi.9.png -------------------------------------------------------------------------------- /frontend/src/assets/google_signin_buttons/android/ldpi/btn_google_signin_dark_normal_ldpi.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/data-science-ucsb/gauchocourses/HEAD/frontend/src/assets/google_signin_buttons/android/ldpi/btn_google_signin_dark_normal_ldpi.9.png -------------------------------------------------------------------------------- /frontend/src/assets/google_signin_buttons/android/ldpi/btn_google_signin_light_focus_ldpi.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/data-science-ucsb/gauchocourses/HEAD/frontend/src/assets/google_signin_buttons/android/ldpi/btn_google_signin_light_focus_ldpi.9.png -------------------------------------------------------------------------------- /frontend/src/assets/google_signin_buttons/android/mdpi/btn_google_signin_dark_focus_mdpi.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/data-science-ucsb/gauchocourses/HEAD/frontend/src/assets/google_signin_buttons/android/mdpi/btn_google_signin_dark_focus_mdpi.9.png -------------------------------------------------------------------------------- /frontend/src/assets/google_signin_buttons/android/mdpi/btn_google_signin_dark_normal_mdpi.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/data-science-ucsb/gauchocourses/HEAD/frontend/src/assets/google_signin_buttons/android/mdpi/btn_google_signin_dark_normal_mdpi.9.png -------------------------------------------------------------------------------- /frontend/src/assets/google_signin_buttons/android/mdpi/btn_google_signin_light_focus_mdpi.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/data-science-ucsb/gauchocourses/HEAD/frontend/src/assets/google_signin_buttons/android/mdpi/btn_google_signin_light_focus_mdpi.9.png -------------------------------------------------------------------------------- /frontend/src/assets/google_signin_buttons/android/xxhdpi/btn_google_dark_disabled_xxhdpi.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/data-science-ucsb/gauchocourses/HEAD/frontend/src/assets/google_signin_buttons/android/xxhdpi/btn_google_dark_disabled_xxhdpi.9.png -------------------------------------------------------------------------------- /frontend/src/assets/google_signin_buttons/android/xxhdpi/btn_google_light_disabled_xxhdpi.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/data-science-ucsb/gauchocourses/HEAD/frontend/src/assets/google_signin_buttons/android/xxhdpi/btn_google_light_disabled_xxhdpi.9.png -------------------------------------------------------------------------------- /frontend/src/assets/google_signin_buttons/android/xxhdpi/btn_google_light_pressed_xxhdpi.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/data-science-ucsb/gauchocourses/HEAD/frontend/src/assets/google_signin_buttons/android/xxhdpi/btn_google_light_pressed_xxhdpi.9.png -------------------------------------------------------------------------------- /frontend/src/assets/google_signin_buttons/android/xxxhdpi/btn_google_dark_normal_xxxhdpi.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/data-science-ucsb/gauchocourses/HEAD/frontend/src/assets/google_signin_buttons/android/xxxhdpi/btn_google_dark_normal_xxxhdpi.9.png -------------------------------------------------------------------------------- /frontend/src/assets/google_signin_buttons/android/xxxhdpi/btn_google_dark_pressed_xxxhdpi.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/data-science-ucsb/gauchocourses/HEAD/frontend/src/assets/google_signin_buttons/android/xxxhdpi/btn_google_dark_pressed_xxxhdpi.9.png -------------------------------------------------------------------------------- /frontend/src/assets/google_signin_buttons/android/xxxhdpi/btn_google_light_focus_xxxhdpi.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/data-science-ucsb/gauchocourses/HEAD/frontend/src/assets/google_signin_buttons/android/xxxhdpi/btn_google_light_focus_xxxhdpi.9.png -------------------------------------------------------------------------------- /frontend/src/assets/google_signin_buttons/android/xxxhdpi/btn_google_light_normal_xxxhdpi.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/data-science-ucsb/gauchocourses/HEAD/frontend/src/assets/google_signin_buttons/android/xxxhdpi/btn_google_light_normal_xxxhdpi.9.png -------------------------------------------------------------------------------- /frontend/src/assets/google_signin_buttons/android/hdpi/btn_google_signin_dark_disabled_hdpi.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/data-science-ucsb/gauchocourses/HEAD/frontend/src/assets/google_signin_buttons/android/hdpi/btn_google_signin_dark_disabled_hdpi.9.png -------------------------------------------------------------------------------- /frontend/src/assets/google_signin_buttons/android/hdpi/btn_google_signin_dark_pressed_hdpi.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/data-science-ucsb/gauchocourses/HEAD/frontend/src/assets/google_signin_buttons/android/hdpi/btn_google_signin_dark_pressed_hdpi.9.png -------------------------------------------------------------------------------- /frontend/src/assets/google_signin_buttons/android/hdpi/btn_google_signin_light_disabled_hdpi.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/data-science-ucsb/gauchocourses/HEAD/frontend/src/assets/google_signin_buttons/android/hdpi/btn_google_signin_light_disabled_hdpi.9.png -------------------------------------------------------------------------------- /frontend/src/assets/google_signin_buttons/android/hdpi/btn_google_signin_light_normal_hdpi.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/data-science-ucsb/gauchocourses/HEAD/frontend/src/assets/google_signin_buttons/android/hdpi/btn_google_signin_light_normal_hdpi.9.png -------------------------------------------------------------------------------- /frontend/src/assets/google_signin_buttons/android/hdpi/btn_google_signin_light_pressed_hdpi.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/data-science-ucsb/gauchocourses/HEAD/frontend/src/assets/google_signin_buttons/android/hdpi/btn_google_signin_light_pressed_hdpi.9.png -------------------------------------------------------------------------------- /frontend/src/assets/google_signin_buttons/android/ldpi/btn_google_signin_dark_disabled_ldpi.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/data-science-ucsb/gauchocourses/HEAD/frontend/src/assets/google_signin_buttons/android/ldpi/btn_google_signin_dark_disabled_ldpi.9.png -------------------------------------------------------------------------------- /frontend/src/assets/google_signin_buttons/android/ldpi/btn_google_signin_dark_pressed_ldpi.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/data-science-ucsb/gauchocourses/HEAD/frontend/src/assets/google_signin_buttons/android/ldpi/btn_google_signin_dark_pressed_ldpi.9.png -------------------------------------------------------------------------------- /frontend/src/assets/google_signin_buttons/android/ldpi/btn_google_signin_light_disabled_ldpi.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/data-science-ucsb/gauchocourses/HEAD/frontend/src/assets/google_signin_buttons/android/ldpi/btn_google_signin_light_disabled_ldpi.9.png -------------------------------------------------------------------------------- /frontend/src/assets/google_signin_buttons/android/ldpi/btn_google_signin_light_normal_ldpi.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/data-science-ucsb/gauchocourses/HEAD/frontend/src/assets/google_signin_buttons/android/ldpi/btn_google_signin_light_normal_ldpi.9.png -------------------------------------------------------------------------------- /frontend/src/assets/google_signin_buttons/android/ldpi/btn_google_signin_light_pressed_ldpi.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/data-science-ucsb/gauchocourses/HEAD/frontend/src/assets/google_signin_buttons/android/ldpi/btn_google_signin_light_pressed_ldpi.9.png -------------------------------------------------------------------------------- /frontend/src/assets/google_signin_buttons/android/mdpi/btn_google_signin_dark_disabled_mdpi.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/data-science-ucsb/gauchocourses/HEAD/frontend/src/assets/google_signin_buttons/android/mdpi/btn_google_signin_dark_disabled_mdpi.9.png -------------------------------------------------------------------------------- /frontend/src/assets/google_signin_buttons/android/mdpi/btn_google_signin_dark_pressed_mdpi.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/data-science-ucsb/gauchocourses/HEAD/frontend/src/assets/google_signin_buttons/android/mdpi/btn_google_signin_dark_pressed_mdpi.9.png -------------------------------------------------------------------------------- /frontend/src/assets/google_signin_buttons/android/mdpi/btn_google_signin_light_disabled_mdpi.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/data-science-ucsb/gauchocourses/HEAD/frontend/src/assets/google_signin_buttons/android/mdpi/btn_google_signin_light_disabled_mdpi.9.png -------------------------------------------------------------------------------- /frontend/src/assets/google_signin_buttons/android/mdpi/btn_google_signin_light_normal_mdpi.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/data-science-ucsb/gauchocourses/HEAD/frontend/src/assets/google_signin_buttons/android/mdpi/btn_google_signin_light_normal_mdpi.9.png -------------------------------------------------------------------------------- /frontend/src/assets/google_signin_buttons/android/mdpi/btn_google_signin_light_pressed_mdpi.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/data-science-ucsb/gauchocourses/HEAD/frontend/src/assets/google_signin_buttons/android/mdpi/btn_google_signin_light_pressed_mdpi.9.png -------------------------------------------------------------------------------- /frontend/src/assets/google_signin_buttons/android/tvdpi/btn_google_signin_dark_focus_tvdpi.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/data-science-ucsb/gauchocourses/HEAD/frontend/src/assets/google_signin_buttons/android/tvdpi/btn_google_signin_dark_focus_tvdpi.9.png -------------------------------------------------------------------------------- /frontend/src/assets/google_signin_buttons/android/tvdpi/btn_google_signin_dark_normal_tvdpi.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/data-science-ucsb/gauchocourses/HEAD/frontend/src/assets/google_signin_buttons/android/tvdpi/btn_google_signin_dark_normal_tvdpi.9.png -------------------------------------------------------------------------------- /frontend/src/assets/google_signin_buttons/android/tvdpi/btn_google_signin_dark_pressed_tvdpi.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/data-science-ucsb/gauchocourses/HEAD/frontend/src/assets/google_signin_buttons/android/tvdpi/btn_google_signin_dark_pressed_tvdpi.9.png -------------------------------------------------------------------------------- /frontend/src/assets/google_signin_buttons/android/tvdpi/btn_google_signin_light_focus_tvdpi.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/data-science-ucsb/gauchocourses/HEAD/frontend/src/assets/google_signin_buttons/android/tvdpi/btn_google_signin_light_focus_tvdpi.9.png -------------------------------------------------------------------------------- /frontend/src/assets/google_signin_buttons/android/tvdpi/btn_google_signin_light_normal_tvdpi.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/data-science-ucsb/gauchocourses/HEAD/frontend/src/assets/google_signin_buttons/android/tvdpi/btn_google_signin_light_normal_tvdpi.9.png -------------------------------------------------------------------------------- /frontend/src/assets/google_signin_buttons/android/xhdpi/btn_google_signin_dark_focus_xhdpi.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/data-science-ucsb/gauchocourses/HEAD/frontend/src/assets/google_signin_buttons/android/xhdpi/btn_google_signin_dark_focus_xhdpi.9.png -------------------------------------------------------------------------------- /frontend/src/assets/google_signin_buttons/android/xhdpi/btn_google_signin_dark_normal_xhdpi.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/data-science-ucsb/gauchocourses/HEAD/frontend/src/assets/google_signin_buttons/android/xhdpi/btn_google_signin_dark_normal_xhdpi.9.png -------------------------------------------------------------------------------- /frontend/src/assets/google_signin_buttons/android/xhdpi/btn_google_signin_dark_pressed_xhdpi.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/data-science-ucsb/gauchocourses/HEAD/frontend/src/assets/google_signin_buttons/android/xhdpi/btn_google_signin_dark_pressed_xhdpi.9.png -------------------------------------------------------------------------------- /frontend/src/assets/google_signin_buttons/android/xhdpi/btn_google_signin_light_focus_xhdpi.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/data-science-ucsb/gauchocourses/HEAD/frontend/src/assets/google_signin_buttons/android/xhdpi/btn_google_signin_light_focus_xhdpi.9.png -------------------------------------------------------------------------------- /frontend/src/assets/google_signin_buttons/android/xhdpi/btn_google_signin_light_normal_xhdpi.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/data-science-ucsb/gauchocourses/HEAD/frontend/src/assets/google_signin_buttons/android/xhdpi/btn_google_signin_light_normal_xhdpi.9.png -------------------------------------------------------------------------------- /frontend/src/assets/google_signin_buttons/android/xxhdpi/btn_google_signin_dark_focus_xxhdpi.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/data-science-ucsb/gauchocourses/HEAD/frontend/src/assets/google_signin_buttons/android/xxhdpi/btn_google_signin_dark_focus_xxhdpi.9.png -------------------------------------------------------------------------------- /frontend/src/assets/google_signin_buttons/android/xxxhdpi/btn_google_dark_disabled_xxxhdpi.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/data-science-ucsb/gauchocourses/HEAD/frontend/src/assets/google_signin_buttons/android/xxxhdpi/btn_google_dark_disabled_xxxhdpi.9.png -------------------------------------------------------------------------------- /frontend/src/assets/google_signin_buttons/android/xxxhdpi/btn_google_light_disabled_xxxhdpi.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/data-science-ucsb/gauchocourses/HEAD/frontend/src/assets/google_signin_buttons/android/xxxhdpi/btn_google_light_disabled_xxxhdpi.9.png -------------------------------------------------------------------------------- /frontend/src/assets/google_signin_buttons/android/xxxhdpi/btn_google_light_pressed_xxxhdpi.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/data-science-ucsb/gauchocourses/HEAD/frontend/src/assets/google_signin_buttons/android/xxxhdpi/btn_google_light_pressed_xxxhdpi.9.png -------------------------------------------------------------------------------- /frontend/src/assets/google_signin_buttons/android/tvdpi/btn_google_signin_dark_disabled_tvdpi.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/data-science-ucsb/gauchocourses/HEAD/frontend/src/assets/google_signin_buttons/android/tvdpi/btn_google_signin_dark_disabled_tvdpi.9.png -------------------------------------------------------------------------------- /frontend/src/assets/google_signin_buttons/android/tvdpi/btn_google_signin_light_disabled_tvdpi.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/data-science-ucsb/gauchocourses/HEAD/frontend/src/assets/google_signin_buttons/android/tvdpi/btn_google_signin_light_disabled_tvdpi.9.png -------------------------------------------------------------------------------- /frontend/src/assets/google_signin_buttons/android/tvdpi/btn_google_signin_light_pressed_tvdpi.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/data-science-ucsb/gauchocourses/HEAD/frontend/src/assets/google_signin_buttons/android/tvdpi/btn_google_signin_light_pressed_tvdpi.9.png -------------------------------------------------------------------------------- /frontend/src/assets/google_signin_buttons/android/xhdpi/btn_google_signin_dark_disabled_xhdpi.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/data-science-ucsb/gauchocourses/HEAD/frontend/src/assets/google_signin_buttons/android/xhdpi/btn_google_signin_dark_disabled_xhdpi.9.png -------------------------------------------------------------------------------- /frontend/src/assets/google_signin_buttons/android/xhdpi/btn_google_signin_light_disabled_xhdpi.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/data-science-ucsb/gauchocourses/HEAD/frontend/src/assets/google_signin_buttons/android/xhdpi/btn_google_signin_light_disabled_xhdpi.9.png -------------------------------------------------------------------------------- /frontend/src/assets/google_signin_buttons/android/xhdpi/btn_google_signin_light_pressed_xhdpi.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/data-science-ucsb/gauchocourses/HEAD/frontend/src/assets/google_signin_buttons/android/xhdpi/btn_google_signin_light_pressed_xhdpi.9.png -------------------------------------------------------------------------------- /frontend/src/assets/google_signin_buttons/android/xxhdpi/btn_google_signin_dark_normal_xxhdpi.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/data-science-ucsb/gauchocourses/HEAD/frontend/src/assets/google_signin_buttons/android/xxhdpi/btn_google_signin_dark_normal_xxhdpi.9.png -------------------------------------------------------------------------------- /frontend/src/assets/google_signin_buttons/android/xxhdpi/btn_google_signin_dark_pressed_xxhdpi.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/data-science-ucsb/gauchocourses/HEAD/frontend/src/assets/google_signin_buttons/android/xxhdpi/btn_google_signin_dark_pressed_xxhdpi.9.png -------------------------------------------------------------------------------- /frontend/src/assets/google_signin_buttons/android/xxhdpi/btn_google_signin_light_focus_xxhdpi.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/data-science-ucsb/gauchocourses/HEAD/frontend/src/assets/google_signin_buttons/android/xxhdpi/btn_google_signin_light_focus_xxhdpi.9.png -------------------------------------------------------------------------------- /frontend/src/assets/google_signin_buttons/android/xxhdpi/btn_google_signin_light_normal_xxhdpi.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/data-science-ucsb/gauchocourses/HEAD/frontend/src/assets/google_signin_buttons/android/xxhdpi/btn_google_signin_light_normal_xxhdpi.9.png -------------------------------------------------------------------------------- /frontend/src/assets/google_signin_buttons/android/xxxhdpi/btn_google_signin_dark_focus_xxxhdpi.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/data-science-ucsb/gauchocourses/HEAD/frontend/src/assets/google_signin_buttons/android/xxxhdpi/btn_google_signin_dark_focus_xxxhdpi.9.png -------------------------------------------------------------------------------- /frontend/src/assets/google_signin_buttons/android/xxhdpi/btn_google_signin_dark_disabled_xxhdpi.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/data-science-ucsb/gauchocourses/HEAD/frontend/src/assets/google_signin_buttons/android/xxhdpi/btn_google_signin_dark_disabled_xxhdpi.9.png -------------------------------------------------------------------------------- /frontend/src/assets/google_signin_buttons/android/xxhdpi/btn_google_signin_light_disabled_xxhdpi.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/data-science-ucsb/gauchocourses/HEAD/frontend/src/assets/google_signin_buttons/android/xxhdpi/btn_google_signin_light_disabled_xxhdpi.9.png -------------------------------------------------------------------------------- /frontend/src/assets/google_signin_buttons/android/xxhdpi/btn_google_signin_light_pressed_xxhdpi.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/data-science-ucsb/gauchocourses/HEAD/frontend/src/assets/google_signin_buttons/android/xxhdpi/btn_google_signin_light_pressed_xxhdpi.9.png -------------------------------------------------------------------------------- /frontend/src/assets/google_signin_buttons/android/xxxhdpi/btn_google_signin_dark_disabled_xxxhdpi.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/data-science-ucsb/gauchocourses/HEAD/frontend/src/assets/google_signin_buttons/android/xxxhdpi/btn_google_signin_dark_disabled_xxxhdpi.9.png -------------------------------------------------------------------------------- /frontend/src/assets/google_signin_buttons/android/xxxhdpi/btn_google_signin_dark_normal_xxxhdpi.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/data-science-ucsb/gauchocourses/HEAD/frontend/src/assets/google_signin_buttons/android/xxxhdpi/btn_google_signin_dark_normal_xxxhdpi.9.png -------------------------------------------------------------------------------- /frontend/src/assets/google_signin_buttons/android/xxxhdpi/btn_google_signin_dark_pressed_xxxhdpi.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/data-science-ucsb/gauchocourses/HEAD/frontend/src/assets/google_signin_buttons/android/xxxhdpi/btn_google_signin_dark_pressed_xxxhdpi.9.png -------------------------------------------------------------------------------- /frontend/src/assets/google_signin_buttons/android/xxxhdpi/btn_google_signin_light_focus_xxxhdpi.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/data-science-ucsb/gauchocourses/HEAD/frontend/src/assets/google_signin_buttons/android/xxxhdpi/btn_google_signin_light_focus_xxxhdpi.9.png -------------------------------------------------------------------------------- /frontend/src/assets/google_signin_buttons/android/xxxhdpi/btn_google_signin_light_normal_xxxhdpi.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/data-science-ucsb/gauchocourses/HEAD/frontend/src/assets/google_signin_buttons/android/xxxhdpi/btn_google_signin_light_normal_xxxhdpi.9.png -------------------------------------------------------------------------------- /frontend/src/assets/google_signin_buttons/android/xxxhdpi/btn_google_signin_light_pressed_xxxhdpi.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/data-science-ucsb/gauchocourses/HEAD/frontend/src/assets/google_signin_buttons/android/xxxhdpi/btn_google_signin_light_pressed_xxxhdpi.9.png -------------------------------------------------------------------------------- /frontend/src/assets/google_signin_buttons/android/xxxhdpi/btn_google_signin_light_disabled_xxxhdpi.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/data-science-ucsb/gauchocourses/HEAD/frontend/src/assets/google_signin_buttons/android/xxxhdpi/btn_google_signin_light_disabled_xxxhdpi.9.png -------------------------------------------------------------------------------- /frontend/jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | transformIgnorePatterns: ['/node_modules/(?!@babel|axios)'], 3 | preset: '@vue/cli-plugin-unit-jest', 4 | "moduleNameMapper": { 5 | "^.+\\.(css|styl|less|sass|scss|png|jpg|ttf|woff|woff2)$": "jest-transform-css" 6 | }, 7 | } 8 | -------------------------------------------------------------------------------- /frontend/public/favicons/site.webmanifest: -------------------------------------------------------------------------------- 1 | {"name":"","short_name":"","icons":[{"src":"/android-chrome-192x192.png","sizes":"192x192","type":"image/png"},{"src":"/android-chrome-512x512.png","sizes":"512x512","type":"image/png"}],"theme_color":"#ffffff","background_color":"#ffffff","display":"standalone"} -------------------------------------------------------------------------------- /frontend/src/plugins/Dayjs.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | import dayjs from 'dayjs'; 3 | import customParseFormat from 'dayjs/plugin/customParseFormat'; 4 | 5 | dayjs.extend(customParseFormat); 6 | 7 | Object.defineProperties(Vue.prototype, { 8 | $date: { 9 | get() { 10 | return dayjs 11 | } 12 | } 13 | }); 14 | -------------------------------------------------------------------------------- /frontend/src/views/NotFound.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 16 | 17 | -------------------------------------------------------------------------------- /frontend/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /dist 4 | 5 | /tests/e2e/reports/ 6 | selenium-debug.log 7 | chromedriver.log 8 | geckodriver.log 9 | 10 | # local env files 11 | .env.local 12 | .env.*.local 13 | 14 | # Log files 15 | npm-debug.log* 16 | yarn-debug.log* 17 | yarn-error.log* 18 | 19 | # Editor directories and files 20 | .idea 21 | .vscode 22 | *.suo 23 | *.ntvs* 24 | *.njsproj 25 | *.sln 26 | *.sw? 27 | -------------------------------------------------------------------------------- /backend/src/main/java/org/gaucho/courses/DTO/ScheduleControllerResponse.java: -------------------------------------------------------------------------------- 1 | package org.gaucho.courses.DTO; 2 | 3 | import lombok.Data; 4 | import org.gaucho.courses.domain.scheduling.Schedule; 5 | 6 | import java.util.List; 7 | 8 | @Data 9 | public class ScheduleControllerResponse { 10 | 11 | List schedules; 12 | List warnings; 13 | List errors; 14 | int lastScheduleIndex; 15 | 16 | } 17 | -------------------------------------------------------------------------------- /backend/src/test/java/org/gaucho/courses/GauchoCoursesApplicationTests.java: -------------------------------------------------------------------------------- 1 | package org.gaucho.courses; 2 | 3 | import org.junit.Test; 4 | import org.junit.runner.RunWith; 5 | import org.springframework.boot.test.context.SpringBootTest; 6 | import org.springframework.test.context.ActiveProfiles; 7 | import org.springframework.test.context.junit4.SpringRunner; 8 | 9 | @RunWith(SpringRunner.class) 10 | @SpringBootTest 11 | @ActiveProfiles("dev") 12 | 13 | public class GauchoCoursesApplicationTests { 14 | 15 | @Test 16 | public void contextLoads() { 17 | } 18 | 19 | } 20 | -------------------------------------------------------------------------------- /frontend/tests/e2e/specs/test.js: -------------------------------------------------------------------------------- 1 | // For authoring Nightwatch tests, see 2 | // https://nightwatchjs.org/guide 3 | 4 | module.exports = { 5 | 'default e2e tests': browser => { 6 | browser 7 | .init() 8 | .waitForElementVisible('#app') 9 | .assert.elementPresent('.hello') 10 | .assert.containsText('h1', 'Welcome to Your Vue.js App') 11 | .assert.elementCount('img', 1) 12 | .end() 13 | }, 14 | 15 | 'example e2e test using a custom command': browser => { 16 | browser 17 | .openHomepage() 18 | .assert.elementPresent('.hello') 19 | .end() 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /backend/src/main/java/org/gaucho/courses/DTO/ScheduleControllerRequest.java: -------------------------------------------------------------------------------- 1 | package org.gaucho.courses.DTO; 2 | 3 | import lombok.Data; 4 | import org.gaucho.courses.domain.remote.ClassSection; 5 | import org.gaucho.courses.domain.remote.CustomEvent; 6 | 7 | import java.util.ArrayList; 8 | import java.util.List; 9 | 10 | @Data 11 | public class ScheduleControllerRequest { 12 | 13 | private List classSections = new ArrayList<>(); 14 | private List customEvents = new ArrayList<>(); 15 | 16 | //private int pageNumber; 17 | private int pageSize = 30; 18 | private int lastScheduleIndex = 0; 19 | 20 | } 21 | -------------------------------------------------------------------------------- /backend/src/main/java/org/gaucho/courses/repository/ScheduleRepository.java: -------------------------------------------------------------------------------- 1 | package org.gaucho.courses.repository; 2 | 3 | import org.gaucho.courses.domain.scheduling.Schedule; 4 | import org.springframework.data.mongodb.repository.MongoRepository; 5 | 6 | import java.util.List; 7 | 8 | public interface ScheduleRepository extends MongoRepository { 9 | 10 | /** 11 | * Returns all schedules that contain the given email. Returns an empty list if 12 | * there are no schedules for the given email. 13 | * @param userEmail User's email address. 14 | */ 15 | List findByUserEmail(String userEmail); 16 | 17 | } 18 | 19 | -------------------------------------------------------------------------------- /backend/src/main/java/org/gaucho/courses/domain/scheduling/CourseAndClassIds.java: -------------------------------------------------------------------------------- 1 | package org.gaucho.courses.domain.scheduling; 2 | 3 | import lombok.Data; 4 | 5 | import java.io.Serializable; 6 | import java.util.ArrayList; 7 | import java.util.List; 8 | 9 | @Data 10 | public class CourseAndClassIds implements Serializable { 11 | 12 | private static final long serialVersionUID = 1L; 13 | 14 | String courseId; // ToDo: This should be a HATEOS reference 15 | 16 | List scheduledEnrollCodes = new ArrayList<>(); // ToDo: this should be a set 17 | 18 | List selectedEnrollCodes = new ArrayList<>(); // ToDo: this should be a set 19 | 20 | String backgroundColor; 21 | } 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # GauchoCourses 2 | 3 | Data Science UCSB's GauchoCourses application is a quarterly course planner that allows students to see possible schedule combinations for the classes they want to take, and save them for when their pass times come around. https://gauchocourses.datascienceucsb.org/#/ 4 | 5 | See [the Wiki](https://github.com/data-science-ucsb/gauchocourses/wiki) for contribution instructions and reference information. The [backend](backend/) and [frontend](frontend/) directories have their own respective README's with more information. 6 | 7 | Having a problem with the app? [Create an issue here](https://github.com/data-science-ucsb/gauchocourses/issues/new). 8 | 9 | Made with <3 by Data Science coders. 10 | -------------------------------------------------------------------------------- /backend/src/main/java/org/gaucho/courses/DTO/ScheduleControllerSaveRequest.java: -------------------------------------------------------------------------------- 1 | package org.gaucho.courses.DTO; 2 | 3 | import lombok.Data; 4 | import org.gaucho.courses.domain.remote.ClassSection; 5 | import org.gaucho.courses.domain.remote.CustomEvent; 6 | import org.gaucho.courses.domain.scheduling.Schedule; 7 | 8 | import java.util.ArrayList; 9 | import java.util.List; 10 | 11 | 12 | @Data 13 | public class ScheduleControllerSaveRequest { 14 | private List selectedClassSections = new ArrayList<>(); 15 | private List scheduledClassSections = new ArrayList<>(); 16 | private List customEvents = new ArrayList<>(); 17 | private Schedule schedule = new Schedule(); 18 | } 19 | -------------------------------------------------------------------------------- /frontend/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | env: { 4 | node: true 5 | }, 6 | 'extends': [ 7 | 'plugin:vue/essential', 8 | 'eslint:recommended' 9 | ], 10 | rules: { 11 | 'no-console': process.env.NODE_ENV === 'production' ? 'error' : 'off', 12 | 'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off', 13 | 'no-console': 'warn', // TODO: Make this an error for pushes to master 14 | 'vue/multi-word-component-names': 'off', 15 | }, 16 | parserOptions: { 17 | parser: '@babel/eslint-parser' 18 | }, 19 | overrides: [ 20 | { 21 | files: [ 22 | '**/__tests__/*.{j,t}s?(x)', 23 | '**/tests/unit/**/*.spec.{j,t}s?(x)' 24 | ], 25 | env: { 26 | jest: true 27 | } 28 | } 29 | ] 30 | } 31 | -------------------------------------------------------------------------------- /.github/workflows/test_pr.yml: -------------------------------------------------------------------------------- 1 | name: 'Test New Pull Requests' 2 | 3 | on: 4 | pull_request: 5 | types: [opened, edited, reopened] 6 | branches: 7 | - gh-actions-pr-issue-5 8 | - main 9 | 10 | jobs: 11 | new_pr_test: 12 | runs-on: ubuntu-latest 13 | name: Start a new docker container to run the tests 14 | steps: 15 | # allow github action workflow access to the repo 16 | - name: checkout repo step 17 | uses: actions/checkout@v3 18 | 19 | # set up the docker container builder 20 | - name: set up docker buildx 21 | uses: docker/setup-buildx-action@v2 22 | 23 | # build the image 24 | - name: build docker image of application 25 | uses: docker/build-push-action@v3 26 | with: 27 | push: false 28 | file: ./Dockerfile 29 | -------------------------------------------------------------------------------- /backend/src/main/java/org/gaucho/courses/domain/remote/CustomEvent.java: -------------------------------------------------------------------------------- 1 | package org.gaucho.courses.domain.remote; 2 | 3 | import com.fasterxml.jackson.annotation.JsonProperty; 4 | import com.fasterxml.jackson.annotation.JsonPropertyOrder; 5 | import lombok.Data; 6 | import lombok.EqualsAndHashCode; 7 | import lombok.extern.slf4j.Slf4j; 8 | import org.gaucho.courses.domain.core.Event; 9 | 10 | import java.io.Serializable; 11 | 12 | @JsonPropertyOrder({ 13 | "name", 14 | "timeLocations", 15 | "backgroundColor" 16 | }) 17 | @Slf4j 18 | @Data 19 | @EqualsAndHashCode(callSuper = true) 20 | public class CustomEvent extends Event implements Serializable { 21 | 22 | @JsonProperty("name") private String name; 23 | @JsonProperty("backgroundColor") private String backgroundColor; 24 | 25 | public CustomEvent() { } 26 | } -------------------------------------------------------------------------------- /frontend/tests/e2e/custom-commands/openHomepageClass.js: -------------------------------------------------------------------------------- 1 | /** 2 | * A class-based Nightwatch custom command which is a variation of the openHomepage.js command. 3 | * The command name is the filename and class needs to contain a "command" method. 4 | * 5 | * Example usage: 6 | * browser.openHomepageClass(); 7 | * 8 | * For more information on writing custom commands see: 9 | * https://nightwatchjs.org/guide/extending-nightwatch/#writing-custom-commands 10 | * 11 | */ 12 | 13 | const assert = require('assert') 14 | 15 | module.exports = class { 16 | async command () { 17 | // Other Nightwatch commands are available via "this.api" 18 | this.api.init() 19 | this.api.waitForElementVisible('#app') 20 | 21 | const result = await this.api.elements('css selector', '#app ul') 22 | assert.strictEqual(result.value.length, 3) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /frontend/public/robots.txt: -------------------------------------------------------------------------------- 1 | # 2 | # robots.txt 3 | # 4 | # This file is to prevent the crawling and indexing of certain parts 5 | # of your site by web crawlers and spiders run by sites like Yahoo! 6 | # and Google. By telling these "robots" where not to go on your site, 7 | # you save bandwidth and server resources. 8 | # 9 | # This file will be ignored unless it is at the root of your host: 10 | # Used: http://example.com/robots.txt 11 | # Ignored: http://example.com/site/robots.txt 12 | # 13 | # For more information about the robots.txt standard, see: 14 | # http://www.robotstxt.org/robotstxt.html 15 | 16 | User-agent: * 17 | # CSS, JS, Images 18 | Allow: /*.css$ 19 | Allow: /*.css? 20 | Allow: /*.js$ 21 | Allow: /*.js? 22 | Allow: /*.gif 23 | Allow: /*.jpg 24 | Allow: /*.jpeg 25 | Allow: /*.png 26 | Allow: /*.svg 27 | # Directories 28 | Allow: /#/ 29 | Allow: /#/about 30 | Disallow: /#/user -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # Docker multi-stage build 2 | 3 | FROM --platform=linux/amd64 maven:3.6.3-openjdk-8 4 | # FROM maven:3.6.3-openjdk-8 5 | 6 | ADD . /project 7 | WORKDIR /project 8 | 9 | RUN mvn clean install -P prod -B -Dorg.slf4j.simpleLogger.log.org.apache.maven.cli.transfer.Slf4jMavenTransferListener=warn 10 | 11 | FROM openjdk:8-jdk 12 | 13 | LABEL maintainer="DS@UCSB" 14 | 15 | COPY --from=0 /project/backend/target/*.jar /app.jar 16 | # COPY /backend/target/*.jar /app.jar 17 | 18 | ENV JAVA_OPTS="" 19 | 20 | EXPOSE 80 21 | ENTRYPOINT ["java", "-jar", "app.jar"] 22 | 23 | 24 | # Guide To Deploy To Heroku, will document this somewhere else later: 25 | # docker build -t gauchocourses --no-cache --platform linux/amd64 . 26 | # docker tag gauchocourses registry.heroku.com/gauchocourses/web 27 | # docker push registry.heroku.com/gauchocourses/web 28 | # heroku container:release web -a gauchocourses -------------------------------------------------------------------------------- /backend/src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | # COMMON PROJECT SETTINGS 2 | # The settinfs on this file are shared between the development and production profiles. For 3 | # more information: https://docs.spring.io/spring-boot/docs/current/reference/html/appendix-application-properties.html 4 | # suppress inspection "UnusedProperty" for whole file 5 | 6 | spring.profiles.active=@spring.profiles.active@ 7 | 8 | server.servlet.session.timeout= 9 | spring.application.name=gauchocourses-backend 10 | 11 | # GauchoCourses config 12 | gauchocourses.proxy.server=api.ucsb.edu 13 | gauchocourses.proxy.port=443 14 | gauchocourses.proxy.scheme=https 15 | gauchocourses.proxy.api-key-name=ucsb-api-key 16 | gauchocourses.proxy.api-key-value= 17 | 18 | # Specify the instrumentation key of your Application Insights resource. 19 | azure.application-insights.instrumentation-key=da2fab5d-c6fb-4688-8823-303dda0c7ad6 20 | -------------------------------------------------------------------------------- /frontend/tests/e2e/custom-commands/openHomepage.js: -------------------------------------------------------------------------------- 1 | /** 2 | * A basic Nightwatch custom command 3 | * which demonstrates usage of ES6 async/await instead of using callbacks. 4 | * The command name is the filename and the exported "command" function is the command. 5 | * 6 | * Example usage: 7 | * browser.openHomepage(); 8 | * 9 | * For more information on writing custom commands see: 10 | * https://nightwatchjs.org/guide/extending-nightwatch/#writing-custom-commands 11 | * 12 | */ 13 | module.exports = { 14 | command: async function () { 15 | // Other Nightwatch commands are available via "this" 16 | // .init() simply calls .url() command with the value of the "launch_url" setting 17 | this.init() 18 | this.waitForElementVisible('#app') 19 | 20 | const result = await this.elements('css selector', '#app ul') 21 | this.assert.strictEqual(result.value.length, 3) 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /backend/src/main/java/org/gaucho/courses/domain/remote/Department.java: -------------------------------------------------------------------------------- 1 | package org.gaucho.courses.domain.remote; 2 | 3 | import com.fasterxml.jackson.annotation.JsonProperty; 4 | import java.io.Serializable; 5 | 6 | import lombok.Data; 7 | 8 | @Data 9 | public class Department implements Serializable { 10 | 11 | private static final long serialVersionUID = 1L; 12 | 13 | @JsonProperty("deptCode") 14 | private String deptCode; 15 | 16 | @JsonProperty("deptTranslationShort") 17 | private String deptTranslationShort; 18 | 19 | @JsonProperty("deptTranslation") 20 | private String deptTranslation; 21 | 22 | @JsonProperty("collegeCode") 23 | private String collegeCode; 24 | 25 | @JsonProperty("divisionCode") 26 | private String divisionCode; 27 | 28 | @JsonProperty("orgCode") 29 | private String orgCode; 30 | 31 | @JsonProperty("inactive") 32 | private String inactive; 33 | } -------------------------------------------------------------------------------- /backend/src/main/java/org/gaucho/courses/config/CustomTelemetryInitializer.java: -------------------------------------------------------------------------------- 1 | package org.gaucho.courses.config; 2 | 3 | import com.microsoft.applicationinsights.extensibility.TelemetryInitializer; 4 | import com.microsoft.applicationinsights.telemetry.Telemetry; 5 | import lombok.extern.slf4j.Slf4j; 6 | import org.springframework.stereotype.Component; 7 | 8 | @Component 9 | @Slf4j 10 | public class CustomTelemetryInitializer implements TelemetryInitializer { 11 | 12 | /** 13 | * Get the slot name from the env var and attach it to the outgoing telemetry. 14 | * @param telemetry Outgoing telemetry 15 | */ 16 | @Override 17 | public void initialize(Telemetry telemetry) { 18 | final String SLOT_ENV_VAR = "SLOT_NAME"; 19 | final String slot = System.getenv(SLOT_ENV_VAR); 20 | if (slot != null) { 21 | log.info("Tagging telemetry with slot name: "+slot); 22 | telemetry.getProperties().put(SLOT_ENV_VAR, slot); 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /backend/src/main/java/org/gaucho/courses/domain/remote/Quarter.java: -------------------------------------------------------------------------------- 1 | package org.gaucho.courses.domain.remote; 2 | 3 | import com.fasterxml.jackson.annotation.*; 4 | import java.io.Serializable; 5 | 6 | import lombok.Data; 7 | 8 | @Data 9 | @JsonInclude(JsonInclude.Include.NON_NULL) 10 | public class Quarter implements Serializable { 11 | 12 | private static final long serialVersionUID = 1L; 13 | 14 | private String quarter; 15 | private String qyy; 16 | private String name; 17 | private String category; 18 | private String academicYear; 19 | private String firstDayOfClasses; 20 | private String lastDayOfClasses; 21 | private String firstDayOfFinals; 22 | private String lastDayOfFinals; 23 | private String firstDayOfQuarter; 24 | private String lastDayOfSchedule; 25 | private String pass1Begin; 26 | private String pass2Begin; 27 | private String pass3Begin; 28 | private String feeDeadline; 29 | private String lastDayToAddUnderGrad; 30 | private String lastDayToAddGrad; 31 | private String lastDayThirdWeek; 32 | 33 | } -------------------------------------------------------------------------------- /frontend/src/components/schedules/BuilderView.vue: -------------------------------------------------------------------------------- 1 | 18 | 19 | 42 | 43 | 46 | -------------------------------------------------------------------------------- /backend/src/main/resources/application-dev.properties: -------------------------------------------------------------------------------- 1 | # DEVELOPMENT SETTINGS 2 | # suppress inspection "UnusedProperty" for whole file 3 | 4 | server.port=8088 5 | 6 | # Data source configs 7 | spring.data.mongodb.host=localhost 8 | spring.data.mongodb.port=27017 9 | 10 | # Oauth client for local development 11 | spring.security.oauth2.client.registration.google.client-id=${GOOGLE_OAUTH_CLIENT_ID_DEV} 12 | spring.security.oauth2.client.registration.google.client-secret=${GOOGLE_OAUTH_CLIENT_SECRET_DEV} 13 | spring.security.oauth2.client.registration.google.authorization-grant-type=authorization_code 14 | 15 | # Disable redis cache for local development 16 | spring.cache.type=NONE 17 | spring.autoconfigure.exclude=org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration,org.springframework.boot.autoconfigure.data.redis.RedisRepositoriesAutoConfiguration 18 | spring.data.redis.repositories.enabled=false 19 | 20 | # Allows access to H2-Database 21 | spring.security.basic.enabled=true 22 | spring.security.basic.authorize-mode=NONE 23 | 24 | server.use-forward-headers=true 25 | server.forward-headers-strategy=framework 26 | -------------------------------------------------------------------------------- /frontend/tests/unit/store/getters.spec.js: -------------------------------------------------------------------------------- 1 | 2 | import getters from '@/store/getters.js'; 3 | 4 | describe('Root Vuex getters', () => { 5 | 6 | describe('userIsAuthenticated', () => { 7 | 8 | it('returns true if the state has the authenticated attribute', () => { 9 | const state = { 10 | user: { 11 | authenticated: true 12 | } 13 | } 14 | 15 | const result = getters.userIsAuthenticated(state); 16 | expect(result).toBe(true); 17 | }) 18 | 19 | it('returns false if the state does not have the authenticated attribute', () => { 20 | const states_to_test = [ 21 | { 22 | user: {} 23 | }, { 24 | user: '' 25 | }, { 26 | user: null 27 | } 28 | ] 29 | 30 | const results = states_to_test.map(state => getters.userIsAuthenticated(state)); 31 | results 32 | .forEach(result => expect(result).toBe(false)) 33 | }) 34 | 35 | }) 36 | }) -------------------------------------------------------------------------------- /frontend/src/router/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import VueRouter from 'vue-router' 3 | import Home from '@/views/Home.vue' 4 | import UserProfile from '@/views/UserProfile.vue' 5 | import NotFound from '@/views/NotFound.vue' 6 | 7 | Vue.use(VueRouter) 8 | 9 | const routes = [ 10 | { 11 | path: '/', 12 | name: 'home', 13 | component: Home, 14 | props: true 15 | }, 16 | { 17 | path: '/about', 18 | name: 'about', 19 | // route level code-splitting 20 | // this generates a separate chunk (about.[hash].js) for this route 21 | // which is lazy-loaded when the route is visited. I'm not sure what the (dis)advantages are 22 | component: () => import(/* webpackChunkName: "about" */ '../views/About.vue') 23 | }, 24 | { 25 | path: '/user', 26 | name: 'user', 27 | component: UserProfile 28 | }, 29 | { 30 | path: '*', 31 | component: NotFound 32 | }, 33 | ] 34 | 35 | // TODO: Use HTML5 history mode for nicer URLs. Requires configuration on the backend. 36 | const router = new VueRouter({ 37 | mode: 'hash', 38 | base: process.env.BASE_URL, 39 | routes 40 | }) 41 | 42 | export default router -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | # This workflow will do a clean installation of node dependencies, cache/restore them, build the source code and run tests across different versions of node 2 | # For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-nodejs 3 | 4 | name: Node.js CI 5 | 6 | on: 7 | pull_request: 8 | branches: [ "main" ] 9 | 10 | jobs: 11 | build: 12 | strategy: 13 | fail-fast: false 14 | matrix: 15 | node-version: [16.x, 18.x] 16 | os: [ubuntu-latest, windows-latest, macos-latest] 17 | runs-on: ${{ matrix.os }} 18 | defaults: 19 | run: 20 | working-directory: frontend 21 | 22 | steps: 23 | - uses: actions/checkout@v3 24 | 25 | - name: Use Node.js ${{ matrix.node-version }} 26 | uses: actions/setup-node@v3 27 | with: 28 | node-version: ${{ matrix.node-version }} 29 | cache: 'npm' 30 | cache-dependency-path: frontend/package-lock.json 31 | 32 | - run: npm ci 33 | - run: npm run build --if-present 34 | - run: npm run test:unit 35 | # - run: npm run test:e2e We don't have any working e2e tests yet 36 | -------------------------------------------------------------------------------- /frontend/tests/e2e/custom-assertions/elementCount.js: -------------------------------------------------------------------------------- 1 | /** 2 | * A custom Nightwatch assertion. The assertion name is the filename. 3 | * 4 | * Example usage: 5 | * browser.assert.elementCount(selector, count) 6 | * 7 | * For more information on custom assertions see: 8 | * https://nightwatchjs.org/guide/extending-nightwatch/#writing-custom-assertions 9 | * 10 | * 11 | * @param {string|object} selectorOrObject 12 | * @param {number} count 13 | */ 14 | 15 | exports.assertion = function elementCount (selectorOrObject, count) { 16 | let selector 17 | 18 | // when called from a page object element or section 19 | if (typeof selectorOrObject === 'object' && selectorOrObject.selector) { 20 | // eslint-disable-next-line prefer-destructuring 21 | selector = selectorOrObject.selector 22 | } else { 23 | selector = selectorOrObject 24 | } 25 | 26 | this.message = `Testing if element <${selector}> has count: ${count}` 27 | this.expected = count 28 | this.pass = val => val === count 29 | this.value = res => res.value 30 | function evaluator (_selector) { 31 | return document.querySelectorAll(_selector).length 32 | } 33 | this.command = cb => this.api.execute(evaluator, [selector], cb) 34 | } 35 | -------------------------------------------------------------------------------- /frontend/tests/e2e/specs/test-with-pageobjects.js: -------------------------------------------------------------------------------- 1 | //////////////////////////////////////////////////////////////// 2 | // For authoring Nightwatch tests, see 3 | // https://nightwatchjs.org/guide 4 | // 5 | // For more information on working with page objects see: 6 | // https://nightwatchjs.org/guide/working-with-page-objects/ 7 | //////////////////////////////////////////////////////////////// 8 | 9 | module.exports = { 10 | beforeEach: (browser) => browser.init(), 11 | 12 | 'e2e tests using page objects': (browser) => { 13 | const homepage = browser.page.homepage() 14 | homepage.waitForElementVisible('@appContainer') 15 | 16 | const app = homepage.section.app 17 | app.assert.elementCount('@logo', 1) 18 | app.expect.section('@welcome').to.be.visible 19 | app.expect.section('@headline').text.to.match(/^Welcome to Your Vue\.js (.*)App$/) 20 | 21 | browser.end() 22 | }, 23 | 24 | 'verify if string "e2e-nightwatch" is within the cli plugin links': (browser) => { 25 | const homepage = browser.page.homepage() 26 | const welcomeSection = homepage.section.app.section.welcome 27 | 28 | welcomeSection.expect.element('@cliPluginLinks').text.to.contain('e2e-nightwatch') 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /frontend/vue.config.js: -------------------------------------------------------------------------------- 1 | // const proxy_url = 'https://gauchocourses-app-prod.happybay-98f6635d.westus.azurecontainerapps.io'; 2 | const proxy_url = 'http://localhost:8088'; 3 | 4 | // vue.config.js 5 | module.exports = { 6 | devServer: { 7 | host: 'localhost', 8 | port: 8080, 9 | proxy: { 10 | /* When on the dev server, this will proxy requests to the Spring Boot backend. 11 | The port needs to match server.port on application-dev.properties. (https://cli.vuejs.org/config/#devserver-proxy) */ 12 | '/remote': { 13 | target: proxy_url, 14 | ws: true, 15 | changeOrigin: true 16 | }, 17 | '/oauth2/authorization/google': { 18 | target: proxy_url, 19 | changeOrigin: true 20 | }, 21 | '/logout': { 22 | target: proxy_url, 23 | changeOrigin: true 24 | }, 25 | '/api': { 26 | target: proxy_url, 27 | ws: true, 28 | changeOrigin: true 29 | }, 30 | '/students': { 31 | target: proxy_url, 32 | ws: true, 33 | changeOrigin: true 34 | } 35 | } 36 | }, 37 | 38 | // Change build paths to make them Maven compatible (https://cli.vuejs.org/config/) 39 | outputDir: 'target/dist', 40 | assetsDir: 'static' 41 | }; 42 | -------------------------------------------------------------------------------- /frontend/tests/e2e/page-objects/homepage.js: -------------------------------------------------------------------------------- 1 | /** 2 | * A Nightwatch page object. The page object name is the filename. 3 | * 4 | * Example usage: 5 | * browser.page.homepage.navigate() 6 | * 7 | * For more information on working with page objects see: 8 | * https://nightwatchjs.org/guide/working-with-page-objects/ 9 | * 10 | */ 11 | 12 | module.exports = { 13 | url: '/', 14 | commands: [], 15 | 16 | // A page object can have elements 17 | elements: { 18 | appContainer: '#app' 19 | }, 20 | 21 | // Or a page objects can also have sections 22 | sections: { 23 | app: { 24 | selector: '#app', 25 | 26 | elements: { 27 | logo: 'img' 28 | }, 29 | 30 | // - a page object section can also have sub-sections 31 | // - elements or sub-sections located here are retrieved using the "app" section as the base 32 | sections: { 33 | headline: { 34 | selector: 'h1' 35 | }, 36 | 37 | welcome: { 38 | // the equivalent css selector for the "welcome" sub-section would be: 39 | // '#app div.hello' 40 | selector: 'div.hello', 41 | 42 | elements: { 43 | cliPluginLinks: { 44 | selector: 'ul', 45 | index: 0 46 | } 47 | } 48 | } 49 | } 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /frontend/tests/e2e/custom-commands/customExecute.js: -------------------------------------------------------------------------------- 1 | /** 2 | * A very basic Nightwatch custom command. The command name is the filename and the 3 | * exported "command" function is the command. 4 | * 5 | * Example usage: 6 | * browser.customExecute(function() { 7 | * console.log('Hello from the browser window') 8 | * }); 9 | * 10 | * For more information on writing custom commands see: 11 | * https://nightwatchjs.org/guide/extending-nightwatch/#writing-custom-commands 12 | * 13 | * @param {*} data 14 | */ 15 | exports.command = function command (data) { 16 | // Other Nightwatch commands are available via "this" 17 | 18 | // .execute() inject a snippet of JavaScript into the page for execution. 19 | // the executed script is assumed to be synchronous. 20 | // 21 | // See https://nightwatchjs.org/api/execute.html for more info. 22 | // 23 | this.execute( 24 | // The function argument is converted to a string and sent to the browser 25 | function (argData) { return argData }, 26 | 27 | // The arguments for the function to be sent to the browser are specified in this array 28 | [data], 29 | 30 | function (result) { 31 | // The "result" object contains the result of what we have sent back from the browser window 32 | console.log('custom execute result:', result.value) 33 | } 34 | ) 35 | 36 | return this 37 | } 38 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "type": "java", 9 | "name": "CodeLens (Launch) - GauchoCoursesApplication", 10 | "request": "launch", 11 | "mainClass": "org.gaucho.courses.GauchoCoursesApplication", 12 | "projectName": "courses" 13 | }, 14 | { 15 | "type": "node", 16 | "request": "launch", 17 | "name": "Launch Program", 18 | "skipFiles": [ 19 | "/**" 20 | ], 21 | "program": "${file}" 22 | }, 23 | { 24 | "type": "node", 25 | "name": "Debug Jest Tests", 26 | "request": "launch", 27 | "runtimeExecutable": "npm", 28 | "cwd": "${workspaceFolder}\\frontend", 29 | "runtimeArgs": [ 30 | "run-script", 31 | "test:vs-code-debug", 32 | "${file}" 33 | ], 34 | "port": 9229, 35 | "console": "integratedTerminal", 36 | "internalConsoleOptions": "neverOpen" 37 | } 38 | ] 39 | } -------------------------------------------------------------------------------- /frontend/tests/unit/components/CustomEventForm.spec.js: -------------------------------------------------------------------------------- 1 | import { createLocalVue, mount } from '@vue/test-utils'; 2 | import CustomEventForm from '@/components/events/CustomEventForm.vue'; 3 | import BootstrapVue from 'bootstrap-vue'; 4 | import { axios_instance } from '@/components/backend-api.js'; 5 | import { customEvent } from '../../testing-objects'; 6 | import flushPromises from "flush-promises"; 7 | const MockAdapter = require("axios-mock-adapter"); 8 | //Import State testing stuff 9 | 10 | // Register BootstrapVue for testing 11 | const localVue = createLocalVue(); 12 | localVue.use(BootstrapVue); 13 | 14 | // Return a mounted PaginatedSchedule with given parameters 15 | const factory = (values = {}) => { 16 | return mount(CustomEventForm, { 17 | localVue, 18 | propsData: { 19 | eventtoedit: customEvent 20 | } 21 | }) 22 | } 23 | 24 | describe('CustomEventForm', () => { 25 | 26 | describe('when the user selects an event to edit', () => { 27 | it('it gets loaded into the component data', () => { 28 | 29 | }); 30 | }); 31 | 32 | describe('a new custom event is saved', () => { 33 | it('it gets added to the store', () => { 34 | 35 | }); 36 | }); 37 | 38 | describe('an input state is changed', () => { 39 | it('the validation states are correct', () => { 40 | 41 | }); 42 | }); 43 | }) 44 | -------------------------------------------------------------------------------- /backend/README.md: -------------------------------------------------------------------------------- 1 | # Backend 2 | 3 | ## Reference Documentation for Spring Components 4 | 5 | For further reference, please consider the following sections: 6 | 7 | - [Official Apache Maven documentation](https://maven.apache.org/guides/index.html) 8 | - [Spring Boot Maven Plugin Reference Guide](https://docs.spring.io/spring-boot/docs/2.1.8.RELEASE/maven-plugin/) 9 | - [Spring Web](https://docs.spring.io/spring-boot/docs/2.1.8.RELEASE/reference/htmlsingle/#boot-features-developing-web-applications) 10 | - [Spring Data JPA](https://docs.spring.io/spring-boot/docs/2.1.8.RELEASE/reference/htmlsingle/#boot-features-jpa-and-spring-data) 11 | - [Spring Security](https://docs.spring.io/spring-boot/docs/2.1.8.RELEASE/reference/htmlsingle/#boot-features-security) 12 | 13 | ## Guides for Spring Components 14 | 15 | The following guides illustrate how to use some features concretely: 16 | 17 | - [Building a RESTful Web Service](https://spring.io/guides/gs/rest-service/) 18 | - [Serving Web Content with Spring MVC](https://spring.io/guides/gs/serving-web-content/) 19 | - [Building REST services with Spring](https://spring.io/guides/tutorials/bookmarks/) 20 | - [Accessing Data with JPA](https://spring.io/guides/gs/accessing-data-jpa/) 21 | - [Securing a Web Application](https://spring.io/guides/gs/securing-web/) 22 | - [Spring Boot and OAuth2](https://spring.io/guides/tutorials/spring-boot-oauth2/) 23 | - [Authenticating a User with LDAP](https://spring.io/guides/gs/authenticating-ldap/) 24 | -------------------------------------------------------------------------------- /backend/src/main/java/org/gaucho/courses/GauchoCoursesApplication.java: -------------------------------------------------------------------------------- 1 | package org.gaucho.courses; 2 | 3 | import org.springframework.beans.factory.annotation.Autowired; 4 | import org.springframework.boot.SpringApplication; 5 | import org.springframework.boot.autoconfigure.SpringBootApplication; 6 | import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; 7 | import org.springframework.cache.annotation.EnableCaching; 8 | import org.springframework.context.annotation.Bean; 9 | import org.springframework.session.data.redis.config.ConfigureRedisAction; 10 | import javax.annotation.PostConstruct; 11 | import com.fasterxml.jackson.databind.ObjectMapper; 12 | import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; 13 | 14 | @EnableCaching 15 | @SpringBootApplication(exclude = {DataSourceAutoConfiguration.class}) 16 | public class GauchoCoursesApplication { 17 | 18 | @Bean 19 | public static ConfigureRedisAction configureRedisAction() { 20 | return ConfigureRedisAction.NO_OP; 21 | } 22 | 23 | // Required if embedded mongo does not work on local architecture 24 | // static { System.setProperty("os.arch", "i686_64"); } 25 | 26 | public static void main(String[] args) { 27 | SpringApplication.run(GauchoCoursesApplication.class, args); 28 | } 29 | 30 | @Autowired 31 | private ObjectMapper jacksonObjectMapper; 32 | 33 | @PostConstruct 34 | public void setUp() { 35 | jacksonObjectMapper.registerModule(new JavaTimeModule()); 36 | } 37 | 38 | } 39 | -------------------------------------------------------------------------------- /backend/src/main/resources/application-prod.properties: -------------------------------------------------------------------------------- 1 | # PRODUCTION PROFILE 2 | # suppress inspection "UnusedProperty" for whole file 3 | server.port=${PORT} # 80 4 | 5 | # Data source configs 6 | spring.data.mongodb.database=gauchocourses 7 | spring.data.mongodb.uri=${ATLAS_MONGODB_URI_PROD} 8 | 9 | # Base cache configs, will need to set up elasticache later 10 | # spring.redis.host=${REDIS_HOST_PROD} 11 | # spring.redis.password=${REDIS_PASSWORD_PROD} 12 | # spring.redis.port=${REDIS_PORT_PROD} 13 | spring.cache.type=NONE 14 | spring.autoconfigure.exclude=org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration,org.springframework.boot.autoconfigure.data.redis.RedisRepositoriesAutoConfiguration 15 | spring.data.redis.repositories.enabled=false 16 | 17 | # Request cache config 18 | # spring.cache.type=redis 19 | # spring.cache.cache-names=quarters,coursesByQuarter,departmentsByQuarter,coursesByQuarterDepartment,courseseByQuarter,classes,departments,quarter 20 | # spring.cache.redis.time-to-live=43200000 21 | 22 | ## Session cache configs 23 | # spring.session.store-type=redis 24 | # spring.session.redis.flush-mode=IMMEDIATE 25 | # spring.session.redis.namespace=spring:session 26 | 27 | # Google oAuth2 configuration 28 | spring.security.oauth2.client.registration.google.client-id=${GOOGLE_OAUTH_CLIENT_ID_PROD} 29 | spring.security.oauth2.client.registration.google.client-secret=${GOOGLE_OAUTH_CLIENT_SECRET_PROD} 30 | spring.security.oauth2.client.registration.google.authorization-grant-type=authorization_code 31 | 32 | server.use-forward-headers=true 33 | server.tomcat.internal-proxies=.* 34 | -------------------------------------------------------------------------------- /backend/src/main/java/org/gaucho/courses/domain/remote/Instructor.java: -------------------------------------------------------------------------------- 1 | package org.gaucho.courses.domain.remote; 2 | 3 | import com.fasterxml.jackson.annotation.*; 4 | 5 | import java.io.Serializable; 6 | import java.util.HashMap; 7 | import java.util.Map; 8 | 9 | @JsonInclude(JsonInclude.Include.NON_NULL) 10 | @JsonPropertyOrder({ 11 | "instructor", 12 | "functionCode" 13 | }) 14 | public class Instructor implements Serializable { 15 | 16 | private static final long serialVersionUID = 1L; 17 | 18 | @JsonProperty("instructor") 19 | private String instructor; 20 | @JsonProperty("functionCode") 21 | private String functionCode; 22 | 23 | @JsonIgnore 24 | private Map additionalProperties = new HashMap(); 25 | 26 | @JsonProperty("instructor") 27 | public String getInstructor() { 28 | return instructor; 29 | } 30 | 31 | @JsonProperty("instructor") 32 | public void setInstructor(String instructor) { 33 | this.instructor = instructor; 34 | } 35 | 36 | @JsonProperty("functionCode") 37 | public String getFunctionCode() { 38 | return functionCode; 39 | } 40 | 41 | @JsonProperty("functionCode") 42 | public void setFunctionCode(String functionCode) { 43 | this.functionCode = functionCode; 44 | } 45 | 46 | @JsonAnyGetter 47 | public Map getAdditionalProperties() { 48 | return this.additionalProperties; 49 | } 50 | 51 | @JsonAnySetter 52 | public void setAdditionalProperty(String name, Object value) { 53 | this.additionalProperties.put(name, value); 54 | } 55 | 56 | } 57 | -------------------------------------------------------------------------------- /backend/src/main/java/org/gaucho/courses/auth/SecurityConfig.java: -------------------------------------------------------------------------------- 1 | package org.gaucho.courses.auth; 2 | 3 | import org.springframework.context.annotation.Configuration; 4 | import org.springframework.security.config.annotation.web.builders.HttpSecurity; 5 | import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; 6 | 7 | import lombok.extern.slf4j.Slf4j; 8 | 9 | /** 10 | * https://spring.io/guides/tutorials/spring-boot-oauth2/ 11 | * https://docs.spring.io/spring-security/site/docs/current/reference/htmlsingle/#oauth2login-provide-websecurityconfigureradapter 12 | * https://docs.spring.io/spring-boot/docs/2.2.1.RELEASE/reference/htmlsingle/#boot-features-security-oauth2-client 13 | */ 14 | @Slf4j 15 | @Configuration 16 | public class SecurityConfig extends WebSecurityConfigurerAdapter { 17 | 18 | /** 19 | * Security adapter applied to every request. 20 | * Default login page: "/oauth2/authorization/google" (NOTE: No ending slash) 21 | * @param http HttpSecurity bean 22 | * @throws Exception If there are problems lol 23 | */ 24 | @Override 25 | protected void configure(HttpSecurity http) throws Exception { 26 | http 27 | .authorizeRequests() 28 | .antMatchers("/h2-console/**", "/", "/**") 29 | .permitAll() 30 | .and() 31 | .oauth2Login() 32 | .defaultSuccessUrl("/#/") 33 | .and() 34 | .logout() 35 | .logoutUrl("/logout") 36 | .logoutSuccessUrl("/#/") 37 | .and() 38 | .csrf() 39 | .disable(); 40 | } 41 | } -------------------------------------------------------------------------------- /backend/src/main/resources/ApplicationInsights.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | da2fab5d-c6fb-4688-8823-303dda0c7ad6 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /frontend/src/store/getters.js: -------------------------------------------------------------------------------- 1 | // Getters for root Vuex 2 | 3 | import { allCombinationsOfLecturesConflict } from '../components/util/event-methods.js' 4 | 5 | export default { 6 | /** 7 | * Returns true if the user is authenticated. False otherwise. 8 | */ 9 | userIsAuthenticated: state => { 10 | if (state.user) { 11 | return state.user['authenticated'] == true; 12 | } else { 13 | return false; 14 | } 15 | }, 16 | 17 | 18 | /** 19 | * Returns an object with basic information about the user. 20 | */ 21 | userInfo: (state, getters) => { 22 | let userInfo; 23 | if (getters.userIsAuthenticated) { 24 | userInfo = { 25 | email: state.user.principal.email, 26 | fullName: state.user.principal.fullName, 27 | photoURL: state.user.principal.picture, 28 | authProvider: state.user.authorizedClientRegistrationId 29 | } 30 | } else { 31 | userInfo = {}; 32 | } 33 | 34 | return userInfo; 35 | }, 36 | 37 | /** 38 | * Returns a flat list of all the selected classSections 39 | */ 40 | selectedClassSections: state => { 41 | return state 42 | .selectedCourses 43 | .flatMap(course => course.classSections) 44 | .filter(class_ => class_.selected == true); 45 | }, 46 | 47 | /** 48 | * Returns boolean indicating if the current selected courses conflict. 49 | */ 50 | selectionsAreConflicting: (state, getters) => { 51 | const lectures = getters.selectedClassSections.filter(c => c.isLecture); 52 | return allCombinationsOfLecturesConflict(lectures) 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /frontend/tests/unit/store/mutations.spec.js: -------------------------------------------------------------------------------- 1 | import mutations from '@/store/mutations.js'; 2 | import {userAttributes} from '../../testing-objects'; 3 | import { axios_instance } from '@/components/backend-api.js'; 4 | import flushPromises from "flush-promises"; 5 | const MockAdapter = require("axios-mock-adapter"); 6 | 7 | 8 | describe('Root Vuex mutations', () => { 9 | 10 | describe('setUserInfo', () => { 11 | let mock; 12 | let state; 13 | const api_path = '/api/user/' 14 | 15 | beforeEach(() => { 16 | mock = new MockAdapter(axios_instance); 17 | state = { 18 | user: {} 19 | }; 20 | }); 21 | 22 | it('sets user info if API response has user principal', async () => { 23 | mock 24 | .onGet(api_path) 25 | .reply(200, userAttributes); 26 | 27 | mutations.setUserInfo(state); 28 | await flushPromises(); 29 | 30 | expect(state.user).toHaveProperty('principal') 31 | }) 32 | 33 | it('does not set user info if API response is empty', async () => { 34 | mock 35 | .onGet(api_path) 36 | .reply(200, ''); 37 | 38 | mutations.setUserInfo(state); 39 | await flushPromises(); 40 | 41 | expect(state.user).toMatchObject({}) 42 | }) 43 | 44 | it('does not set user info if there is a 500-level error', async () => { 45 | mock 46 | .onGet(api_path) 47 | .reply(500, ''); 48 | 49 | mutations.setUserInfo(state); 50 | await flushPromises(); 51 | 52 | expect(state.user).toMatchObject({}) 53 | }) 54 | 55 | }) 56 | 57 | 58 | 59 | }) -------------------------------------------------------------------------------- /backend/src/main/java/org/gaucho/courses/domain/remote/GeneralEducation.java: -------------------------------------------------------------------------------- 1 | package org.gaucho.courses.domain.remote; 2 | 3 | import java.util.HashMap; 4 | import java.util.Map; 5 | import com.fasterxml.jackson.annotation.JsonAnyGetter; 6 | import com.fasterxml.jackson.annotation.JsonAnySetter; 7 | import com.fasterxml.jackson.annotation.JsonIgnore; 8 | import com.fasterxml.jackson.annotation.JsonInclude; 9 | import com.fasterxml.jackson.annotation.JsonProperty; 10 | import com.fasterxml.jackson.annotation.JsonPropertyOrder; 11 | 12 | import java.io.Serializable; 13 | 14 | @JsonInclude(JsonInclude.Include.NON_NULL) 15 | @JsonPropertyOrder({ 16 | "geCode", 17 | "geCollege" 18 | }) 19 | public class GeneralEducation implements Serializable { 20 | 21 | private static final long serialVersionUID = 1L; 22 | 23 | @JsonProperty("geCode") 24 | private String geCode; 25 | @JsonProperty("geCollege") 26 | private String geCollege; 27 | 28 | @JsonIgnore 29 | private Map additionalProperties = new HashMap(); 30 | 31 | @JsonProperty("geCode") 32 | public String getGeCode() { 33 | return geCode; 34 | } 35 | 36 | @JsonProperty("geCode") 37 | public void setGeCode(String geCode) { 38 | this.geCode = geCode; 39 | } 40 | 41 | @JsonProperty("geCollege") 42 | public String getGeCollege() { 43 | return geCollege; 44 | } 45 | 46 | @JsonProperty("geCollege") 47 | public void setGeCollege(String geCollege) { 48 | this.geCollege = geCollege; 49 | } 50 | 51 | @JsonAnyGetter 52 | public Map getAdditionalProperties() { 53 | return this.additionalProperties; 54 | } 55 | 56 | @JsonAnySetter 57 | public void setAdditionalProperty(String name, Object value) { 58 | this.additionalProperties.put(name, value); 59 | } 60 | 61 | } -------------------------------------------------------------------------------- /frontend/src/components/schedules/ScheduleView.vue: -------------------------------------------------------------------------------- 1 | 18 | 19 | 66 | 67 | 79 | -------------------------------------------------------------------------------- /frontend/tests/e2e/specs/ClassList.spec.js: -------------------------------------------------------------------------------- 1 | import { createLocalVue, mount } from '@vue/test-utils'; 2 | import ClassList from '@/components/events/ClassList.vue'; 3 | import BootstrapVue from 'bootstrap-vue'; 4 | import { axios_instance } from '@/components/backend-api.js'; 5 | import { courses, quarters } from '../../testing-objects'; 6 | import flushPromises from "flush-promises"; 7 | const MockAdapter = require("axios-mock-adapter"); 8 | 9 | // Register BootstrapVue for testing 10 | const localVue = createLocalVue(); 11 | localVue.use(BootstrapVue); 12 | 13 | // Return a mounted PaginatedSchedule with given parameters 14 | const factory = (values = {}) => { 15 | return mount(ClassList, { 16 | localVue, 17 | propsData: values 18 | }) 19 | } 20 | 21 | 22 | describe('ClassList', () => { 23 | let mock; 24 | 25 | beforeEach(() => { 26 | mock = new MockAdapter(axios_instance); 27 | }); 28 | 29 | afterAll(() => { 30 | //mock.restore(); 31 | }); 32 | 33 | describe('when created without a course prop', () => { 34 | var wrapper = factory(); 35 | 36 | it('displays placeholder text', () => { 37 | expect(wrapper.find('#placeholder').exists()).toBe(true); 38 | }) 39 | }) 40 | 41 | describe('when created with a course prop', () => { 42 | var wrapper = factory({ 43 | course: courses[0] 44 | }); 45 | 46 | it('lists all the class sections', () => { 47 | expect(wrapper.findAll('tr').length).toBe(3); 48 | }) 49 | 50 | it('adds the course to the list of courses', () => { 51 | 52 | }) 53 | 54 | it('selects all class sections by default', () => { 55 | expect(wrapper.vm.classesChosen).toBe([true, true]); 56 | }) 57 | }) 58 | 59 | describe('when the user adds the classes', () => { 60 | 61 | it('the list of classSections is updated accordingly', () => { 62 | 63 | }) 64 | }); 65 | 66 | 67 | }) -------------------------------------------------------------------------------- /frontend/src/main.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import App from './App.vue' 3 | import router from './router' 4 | import store from './store' 5 | import BootstrapVue from 'bootstrap-vue' 6 | import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome' 7 | import { library } from '@fortawesome/fontawesome-svg-core' 8 | import { faInfoCircle, faEdit, faUndo, 9 | faFilter, faCalendarPlus, faTrashAlt, faPlusSquare, 10 | faExclamationCircle, faStar, faHeart, faThumbtack, faBorderAll, faColumns, faCalendar, faList, faChevronRight, faChevronDown, faCheck, faPencilAlt, faChevronUp, faFileDownload} from '@fortawesome/free-solid-svg-icons' 11 | import 'bootstrap/dist/css/bootstrap.css' 12 | import 'bootstrap-vue/dist/bootstrap-vue.css' 13 | import '@/plugins/Dayjs'; 14 | import VueDayjs from 'vue-dayjs-plugin' 15 | import Verte from 'verte'; 16 | import 'verte/dist/verte.css'; 17 | 18 | // Configure font awesome 19 | library.add(faInfoCircle, faUndo, faFilter, faCalendarPlus, faPlusSquare, faTrashAlt, faEdit, faExclamationCircle, faStar, faHeart, faThumbtack, faBorderAll, faColumns, faCalendar, faList, faChevronRight, faChevronDown, faCheck, faPencilAlt, faChevronUp, faFileDownload); 20 | 21 | Vue.component('font-awesome-icon', FontAwesomeIcon); 22 | 23 | // Configure Bootstrap-Vue 24 | Vue.use(BootstrapVue, VueDayjs); 25 | 26 | Vue.component(Verte.name, Verte); 27 | 28 | // Configure global event hub 29 | Vue.prototype.$eventHub = new Vue(); 30 | 31 | // Subscribe to mutations on the Vuex store and cache the store on the browser's local storage 32 | store.subscribe((mutation, state) => localStorage.setItem('store', JSON.stringify({...state, 'user': undefined}))); 33 | 34 | // Turn off the annoying "production tip" log 35 | Vue.config.productionTip = false; 36 | 37 | // Root Vue Instance (https://vuejs.org/v2/guide/instance.html) 38 | new Vue({ 39 | router, 40 | store, 41 | render: h => h(App), 42 | beforeCreate() { 43 | this.$store.commit('initializeStore'); 44 | this.$store.dispatch('setUserInfoAsync'); 45 | } 46 | }).$mount('#app'); -------------------------------------------------------------------------------- /frontend/src/assets/google_signin_buttons/web/vector/btn_google_dark_disabled_ios.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | btn_google_dark_disabled_ios 5 | Created with Sketch. 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /frontend/src/assets/google_signin_buttons/web/vector/btn_google_light_disabled_ios.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | btn_google_light_disabled_ios 5 | Created with Sketch. 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /.github/workflows/deploy.yml: -------------------------------------------------------------------------------- 1 | name: Pushes Docker image to GitHub container registry 2 | 3 | on: 4 | release: 5 | types: [created] 6 | branches: 7 | - main 8 | - issue-4 9 | 10 | jobs: 11 | 12 | build_and_push_to_github: 13 | name: Push Docker image to GitHub Packages 14 | runs-on: ubuntu-latest 15 | permissions: 16 | contents: read 17 | packages: write 18 | steps: 19 | - name: Check out the repo 20 | uses: actions/checkout@v3 21 | 22 | - name: Get tag 23 | id: repository 24 | run: echo "::set-output name=tag::$(git describe --tags HEAD)" 25 | 26 | - name: Set up Docker Buildx 27 | uses: docker/setup-buildx-action@v2 28 | with: 29 | drive: docker 30 | install: true 31 | 32 | - name: Setup AWS ECR details 33 | uses: aws-actions/configure-aws-credentials@v1 34 | with: 35 | aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} 36 | aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} 37 | aws-region: us-east-1 38 | 39 | - name: Login to AWS ECR 40 | id: login-pf-aws-ecr 41 | uses: aws-actions/amazon-ecr-login@v1 42 | 43 | - name: Build and push to AWS ECR 44 | run: | 45 | docker build -t 380357184503.dkr.ecr.us-east-1.amazonaws.com/gauchocourses-cr:${{ github.sha }} . 46 | docker images 47 | docker push 380357184503.dkr.ecr.us-east-1.amazonaws.com/gauchocourses-cr:${{ github.sha }} 48 | 49 | - name: Log in to GitHub Container Registry 50 | uses: docker/login-action@v2 51 | with: 52 | registry: ghcr.io 53 | username: ${{ github.actor }} 54 | password: ${{ secrets.GITHUB_TOKEN }} 55 | 56 | - name: Build and Push to GitHub Packages 57 | uses: docker/build-push-action@v2 58 | with: 59 | context: . 60 | push: true 61 | tags: ghcr.io/${{github.repository}}:${{steps.repository.outputs.tag}} 62 | # registry: docker.pkg.github.com 63 | # repository: data-science-ucsb/gauchocourses/gauchocourses:${{ github.event.release.tag_name }} -------------------------------------------------------------------------------- /frontend/src/components/events/ListItems/CustomEventListItem.vue: -------------------------------------------------------------------------------- 1 | 22 | 23 | 64 | 65 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | org.gaucho 7 | courses 8 | 0.0.1-SNAPSHOT 9 | pom 10 | 11 | GauchoCourses 12 | Open source class schedule organizer for UCSB students. 13 | 14 | 15 | ${project.basedir} 16 | 17 | 18 | 0.8.5 19 | 4.3.0 20 | 21 | 22 | 23 | frontend 24 | backend 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | -------------------------------------------------------------------------------- /frontend/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | GauchoCourses 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 43 | 44 | 45 | 46 | 47 | 50 | 51 |
52 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /frontend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "frontend", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "serve": "vue-cli-service serve", 7 | "dev": "concurrently --kill-others \"npm run serve\" \"mvn -f ../backend/pom.xml spring-boot:run\" ", 8 | "build": "vue-cli-service build", 9 | "test:unit": "vue-cli-service test:unit", 10 | "test:e2e": "vue-cli-service test:e2e", 11 | "test:vs-code-debug": "node --inspect-brk ./node_modules/@vue/cli-service/bin/vue-cli-service.js test:unit --no-cache --watch --runInBand", 12 | "lint": "vue-cli-service lint" 13 | }, 14 | "dependencies": { 15 | "@fortawesome/fontawesome-svg-core": "^1.2.27", 16 | "@fortawesome/free-solid-svg-icons": "^5.12.1", 17 | "@fortawesome/vue-fontawesome": "^0.1.9", 18 | "@fullcalendar/core": "^6.1.4", 19 | "@fullcalendar/daygrid": "^6.1.4", 20 | "@fullcalendar/timegrid": "^6.1.4", 21 | "@fullcalendar/vue": "^6.1.4", 22 | "@microsoft/applicationinsights-web": "^2.5.5", 23 | "axios": "^1.3.4", 24 | "axios-retry": "^3.1.8", 25 | "bootstrap": "^4.3.1", 26 | "bootstrap-table": "^1.15.5", 27 | "bootstrap-vue": "^2.8.0", 28 | "core-js": "^3.1.2", 29 | "css-loader": "^3.2.0", 30 | "dayjs": "^1.9.7", 31 | "flush-promises": "^1.0.2", 32 | "fullcalendar": "^6.1.4", 33 | "html2canvas": "^1.4.1", 34 | "jest-canvas-mock": "^2.4.0", 35 | "jest-transform-css": "^6.0.0", 36 | "jquery": "^3.5.0", 37 | "jspdf": "^2.5.1", 38 | "sass": "^1.56.1", 39 | "sass-loader": "^8.0.0", 40 | "verte": "^0.0.12", 41 | "vue": "^2.6.10", 42 | "vue-application-insights": "^1.0.7", 43 | "vue-dayjs-plugin": "^1.0.0", 44 | "vue-router": "^3.0.6", 45 | "vue-slider-component": "^3.1.1", 46 | "vuex": "^3.0.1", 47 | "xss": "^1.0.6" 48 | }, 49 | "devDependencies": { 50 | "@babel/eslint-parser": "^7.19.1", 51 | "@vue/cli-plugin-babel": "^5.0.8", 52 | "@vue/cli-plugin-e2e-nightwatch": "^5.0.8", 53 | "@vue/cli-plugin-eslint": "^5.0.8", 54 | "@vue/cli-plugin-router": "^5.0.8", 55 | "@vue/cli-plugin-unit-jest": "^5.0.8", 56 | "@vue/cli-plugin-vuex": "^5.0.8", 57 | "@vue/cli-service": "^5.0.8", 58 | "@vue/test-utils": "1.0.0-beta.29", 59 | "@vue/vue2-jest": "^27.0.0", 60 | "axios-mock-adapter": "^1.17.0", 61 | "chromedriver": "107.0.3", 62 | "concurrently": "^5.2.0", 63 | "eslint": "^8.28.0", 64 | "eslint-plugin-vue": "^9.8.0", 65 | "geckodriver": "^3.2.0", 66 | "vue-template-compiler": "^2.6.10" 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /frontend/README.md: -------------------------------------------------------------------------------- 1 | # Frontend 2 | 3 | Project structure adapted from [this template](https://github.com/jonashackt/spring-boot-vuejs). 4 | 5 | Make sure you're in `/frontend` before running these commands. 6 | 7 | ## Project setup 8 | ``` 9 | npm install 10 | ``` 11 | 12 | ### Compiles and hot-reloads for development, runs backend concurrently 13 | ``` 14 | npm run dev 15 | ``` 16 | 17 | ### Compiles and hot-reloads for development 18 | ``` 19 | npm run serve 20 | ``` 21 | 22 | ### Compiles and minifies for production 23 | ``` 24 | npm run build 25 | ``` 26 | 27 | ### Run your unit tests 28 | ``` 29 | npm run test:unit 30 | ``` 31 | 32 | ### Run your end-to-end tests 33 | ``` 34 | npm run test:e2e 35 | ``` 36 | 37 | ### Lints and fixes files 38 | ``` 39 | npm run lint 40 | ``` 41 | 42 | ### Customize configuration 43 | See [Configuration Reference](https://cli.vuejs.org/config/). 44 | 45 | ## Bootstrap-Vue 46 | 47 | The project is configured to use [Bootstrap-Vue](https://bootstrap-vue.js.org/docs/components/). Add any components you 48 | 49 | ## Font Awesome Icons 50 | 51 | The project is configured to use Font Awesome icons. To add an icon, first go to the Font Awesome icon list and find an appropriate icon. 52 | 53 | 1. Go to [`main.js`](src/main.js) and add the icon name to the import statement from `@fortawesome/free-solid-svg-icons`. This import has code-completion, so you should be able to find the icon name quickly. 54 | 1. Still in [`main.js`](src/main.js), add the imported object to the `library.add(...)` statement. 55 | 1. You can now add the icon to your Vue components and views. Instantiate a new component with `` 56 | 57 | You can configure the look and feel of the icons as well. See the [reference documentation](https://github.com/FortAwesome/vue-fontawesome#the-icon-property) for more information. 58 | 59 | ## /views vs /components 60 | 61 | Both the `/views` and `/components` directories hold Vue components. The components in `/views` act as _views_ for routing, as in the router will map one or more URLs to the component. If you look at `/router/index.js`, the URLs are mapped to `About.vue` and `Home.vue`. On the other hand, `/components` holds any Vue components which wil not be routed to. 62 | 63 | You can think of views as pages. For example, `/views` could have a `User.vue` for a user profile, `Settings.vue` for a settings screen, etc. 64 | 65 | On the other hand, `/components` has our `backend-api.js` because that will be needed across the different "pages". It also has the "HelloWorld" template, which can be populated with different data for different views. 66 | 67 | :) 68 | -------------------------------------------------------------------------------- /backend/src/main/java/org/gaucho/courses/auth/UserController.java: -------------------------------------------------------------------------------- 1 | package org.gaucho.courses.auth; 2 | 3 | import org.springframework.security.core.Authentication; 4 | import org.springframework.security.core.context.SecurityContextHolder; 5 | import org.springframework.http.HttpStatus; 6 | import org.springframework.http.ResponseEntity; 7 | import org.springframework.security.authentication.AnonymousAuthenticationToken; 8 | import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken; 9 | import org.springframework.web.bind.annotation.RequestMapping; 10 | import org.springframework.web.bind.annotation.RestController; 11 | 12 | import java.util.Optional; 13 | 14 | @RestController 15 | @RequestMapping(value = "api/user/") 16 | public class UserController { 17 | 18 | /** 19 | * REST controller to return information on the currently authenticated user. If the user is not 20 | * logged in, this will return an empty response. If there is some problem retrieving the token, 21 | * this will fail and return an 500-level response. 22 | * @return The OAuth2 authentication token. (null if user is anonymous). 23 | */ 24 | @RequestMapping("/") 25 | public ResponseEntity userAuthentication() { 26 | if (getUserAuthentication().isPresent()) { 27 | return ResponseEntity 28 | .status(HttpStatus.OK) 29 | .body(getUserAuthentication().get()); 30 | } else { 31 | return ResponseEntity 32 | .status(HttpStatus.OK) 33 | .body(null); 34 | } 35 | } 36 | 37 | /** 38 | * Returns Optional with the Oauth2 token for the currently authenticated user. If the user is not logged in with 39 | * OAuth, then the Optional will be empty. 40 | * @return An Optional that may contain the authentication token. 41 | */ 42 | public Optional getUserAuthentication() { 43 | Authentication token = SecurityContextHolder.getContext().getAuthentication(); 44 | 45 | if (token instanceof AnonymousAuthenticationToken) { 46 | return Optional.empty(); 47 | } else if (token.isAuthenticated()) { 48 | return Optional.of((OAuth2AuthenticationToken) token); 49 | } else { 50 | return Optional.empty(); 51 | } 52 | } 53 | 54 | /** 55 | * Returns the email address for the currently authenticated user. 56 | * @return The authenticated user's email. (null if user is anonymous). 57 | */ 58 | public String getUserEmail() { 59 | Optional token = getUserAuthentication(); 60 | 61 | if (token.isPresent()) { 62 | return token.get().getPrincipal().getAttribute("email"); 63 | } else { 64 | return null; 65 | } 66 | } 67 | 68 | } 69 | -------------------------------------------------------------------------------- /frontend/tests/e2e/globals.js: -------------------------------------------------------------------------------- 1 | /////////////////////////////////////////////////////////////////////////////////// 2 | // Refer to the entire list of global config settings here: 3 | // https://github.com/nightwatchjs/nightwatch/blob/master/lib/settings/defaults.js#L16 4 | // 5 | // More info on test globals: 6 | // https://nightwatchjs.org/gettingstarted/configuration/#test-globals 7 | // 8 | /////////////////////////////////////////////////////////////////////////////////// 9 | 10 | module.exports = { 11 | // this controls whether to abort the test execution when an assertion failed and skip the rest 12 | // it's being used in waitFor commands and expect assertions 13 | abortOnAssertionFailure: true, 14 | 15 | // this will overwrite the default polling interval (currently 500ms) for waitFor commands 16 | // and expect assertions that use retry 17 | waitForConditionPollInterval: 500, 18 | 19 | // default timeout value in milliseconds for waitFor commands and implicit waitFor value for 20 | // expect assertions 21 | waitForConditionTimeout: 5000, 22 | 23 | 'default': { 24 | /* 25 | The globals defined here are available everywhere in any test env 26 | */ 27 | 28 | /* 29 | myGlobal: function() { 30 | return 'I\'m a method'; 31 | } 32 | */ 33 | }, 34 | 35 | 'firefox': { 36 | /* 37 | The globals defined here are available only when the chrome testing env is being used 38 | i.e. when running with --env firefox 39 | */ 40 | /* 41 | * myGlobal: function() { 42 | * return 'Firefox specific global'; 43 | * } 44 | */ 45 | }, 46 | 47 | ///////////////////////////////////////////////////////////////// 48 | // Global hooks 49 | // - simple functions which are executed as part of the test run 50 | // - take a callback argument which can be called when an async 51 | // async operation is finished 52 | ///////////////////////////////////////////////////////////////// 53 | /** 54 | * executed before the test run has started, so before a session is created 55 | */ 56 | /* 57 | before(cb) { 58 | //console.log('global before') 59 | cb(); 60 | }, 61 | */ 62 | 63 | /** 64 | * executed before every test suite has started 65 | */ 66 | /* 67 | beforeEach(browser, cb) { 68 | //console.log('global beforeEach') 69 | cb(); 70 | }, 71 | */ 72 | 73 | /** 74 | * executed after every test suite has ended 75 | */ 76 | /* 77 | afterEach(browser, cb) { 78 | browser.perform(function() { 79 | //console.log('global afterEach') 80 | cb(); 81 | }); 82 | }, 83 | */ 84 | 85 | /** 86 | * executed after the test run has finished 87 | */ 88 | /* 89 | after(cb) { 90 | //console.log('global after') 91 | cb(); 92 | }, 93 | */ 94 | 95 | ///////////////////////////////////////////////////////////////// 96 | // Global reporter 97 | // - define your own custom reporter 98 | ///////////////////////////////////////////////////////////////// 99 | /* 100 | reporter(results, cb) { 101 | cb(); 102 | } 103 | */ 104 | } 105 | -------------------------------------------------------------------------------- /frontend/src/components/util/color-utils.js: -------------------------------------------------------------------------------- 1 | 2 | // A list of colors that will be used for the event background and border colors. These are 3 | // chosen such that they show well with black text on top. 4 | let userBackgroundColors = {} 5 | 6 | const backgroundColors = [ 7 | "Aquamarine", 8 | "Azure", 9 | "Chocolate", 10 | "PaleTurquoise", 11 | "Plum", 12 | "Salmon", 13 | "Coral", 14 | "CornflowerBlue", 15 | "DarkKhaki", 16 | "GoldenRod", 17 | "IndianRed", 18 | "LightSeaGreen", 19 | "LightGreen", 20 | "LightSteelBlue", 21 | "LightSlateGray", 22 | "MediumPurple", 23 | "Moccasin", 24 | "PaleVioletRed", 25 | "Turquoise", 26 | "Thistle", 27 | "Tan", 28 | "LightBlue", 29 | "LightCyan", 30 | "LightPink", 31 | "LightSalmon", 32 | "Linen", 33 | "Orange", 34 | "Orchid", 35 | "PowderBlue", 36 | "RosyBrown", 37 | "YellowGreen", 38 | '#69DDFF', 39 | '#96CDFF', 40 | '#D8E1FF', 41 | '#DBBADD', 42 | '#99EDCC', 43 | '#EBF5EE', 44 | '#FBBE7C', 45 | '#FF8F8F', 46 | '#F9B9B7', 47 | '#9BFCFF' 48 | ] 49 | 50 | // A list of colors for the border. Darker than the background colors. 51 | const borderColors = [ 52 | '#0B273E', 53 | '#2F3330', 54 | '#000000', 55 | '#BFACAA', 56 | '#0A2398', 57 | '#6B0E13', 58 | '#147900', 59 | '#020924', 60 | '#212422', 61 | '#8B7877', 62 | '#6B0E13', 63 | '#52070A', 64 | '#0E5C00', 65 | ] 66 | 67 | export function getHash (str) { 68 | var hash = 0; 69 | if (str == null || str.length == 0) return hash; 70 | else { 71 | str = str.replace(/\s/g, ""); 72 | for (let i = 0; i < str.length; i++) { 73 | let char = str.charCodeAt(i); 74 | hash = ((hash<<5)-hash) + char; 75 | hash = hash & hash; // Convert to 32bit integer 76 | } 77 | return hash; 78 | } 79 | } 80 | 81 | /** 82 | * Returns a CSS color from the string. 83 | * @param string A string 84 | */ 85 | function getColor(string, colors) { 86 | const hashCode = getHash(string); 87 | const index = Math.abs(hashCode % colors.length); 88 | return colors[index]; 89 | } 90 | 91 | /** 92 | * Returns an appropriate background color based on the given string. 93 | * @param {string} string 94 | */ 95 | export function getBackgroundColor(string) { 96 | string = string.replace(/\s/g, ""); 97 | if (string in userBackgroundColors) { 98 | return userBackgroundColors[string]; 99 | } 100 | return getColor(string, backgroundColors); 101 | } 102 | 103 | /** 104 | * Returns an appropriate border color based on the given string. 105 | * @param {string} string 106 | */ 107 | export function getBorderColor(string) { 108 | return getColor(string, borderColors); 109 | } 110 | 111 | /** 112 | * Stores a background color identified by the given string. 113 | * @param {string} string 114 | * @param {string} color 115 | */ 116 | export function setBackgroundColor(string, color) { 117 | string = string.replace(/\s/g, ""); 118 | userBackgroundColors[string] = color; 119 | } -------------------------------------------------------------------------------- /frontend/src/components/events/ListItems/EventListItem.vue: -------------------------------------------------------------------------------- 1 | 36 | 37 | 80 | 81 | -------------------------------------------------------------------------------- /backend/src/main/java/org/gaucho/courses/domain/remote/ClassSection.java: -------------------------------------------------------------------------------- 1 | package org.gaucho.courses.domain.remote; 2 | 3 | import com.fasterxml.jackson.annotation.JsonIgnore; 4 | import com.fasterxml.jackson.annotation.JsonProperty; 5 | import com.fasterxml.jackson.annotation.JsonPropertyOrder; 6 | import lombok.Data; 7 | import lombok.EqualsAndHashCode; 8 | import lombok.extern.slf4j.Slf4j; 9 | import org.gaucho.courses.domain.core.Event; 10 | 11 | import java.io.Serializable; 12 | import java.util.HashMap; 13 | import java.util.List; 14 | import java.util.Map; 15 | 16 | @JsonPropertyOrder({ 17 | "enrollCode", 18 | "section", 19 | "session", 20 | "timeLocations", 21 | "classClosed", 22 | "courseCancelled", 23 | "gradingOptionCode", 24 | "enrolledTotal", 25 | "maxEnroll", 26 | "secondaryStatus", 27 | "departmentApprovalRequired", 28 | "instructorApprovalRequired", 29 | "restrictionLevel", 30 | "restrictionMajor", 31 | "restrictionMajorPass", 32 | "restrictionMinor", 33 | "restrictionMinorPass", 34 | "concurrentCourses", 35 | "instructors", 36 | "backgroundColor" 37 | }) 38 | @Slf4j 39 | @Data 40 | @EqualsAndHashCode(callSuper = true) 41 | public class ClassSection extends Event implements Serializable { 42 | 43 | private static final long serialVersionUID = 1L; 44 | 45 | @JsonProperty("enrollCode") private String enrollCode; 46 | @JsonProperty("section") private String section; 47 | @JsonProperty("session") private String session; 48 | @JsonProperty("classClosed") private String classClosed; 49 | @JsonProperty("courseCancelled") private String courseCancelled; 50 | @JsonProperty("gradingOptionCode") private String gradingOptionCode; 51 | @JsonProperty("enrolledTotal") private Integer enrolledTotal; 52 | @JsonProperty("maxEnroll") private Integer maxEnroll; 53 | @JsonProperty("secondaryStatus") private String secondaryStatus; 54 | @JsonProperty("departmentApprovalRequired") private Boolean departmentApprovalRequired; 55 | @JsonProperty("instructorApprovalRequired") private Boolean instructorApprovalRequired; 56 | @JsonProperty("restrictionLevel") private String restrictionLevel; 57 | @JsonProperty("restrictionMajor") private String restrictionMajor; 58 | @JsonProperty("restrictionMajorPass") private String restrictionMajorPass; 59 | @JsonProperty("restrictionMinor") private String restrictionMinor; 60 | @JsonProperty("restrictionMinorPass") private String restrictionMinorPass; 61 | 62 | @JsonProperty("concurrentCourses") private List concurrentCourses = null; 63 | @JsonProperty("instructors") private List instructors = null; 64 | 65 | // Calculated by the frontend, not an API-native property. 66 | @JsonProperty("lectureSectionGroup") private String lectureSectionGroup; 67 | 68 | // Calculated by the frontend, not an API-native property. 69 | @JsonProperty("courseId") private String courseId; 70 | 71 | @JsonProperty("isLecture") 72 | public boolean isLecture(){ 73 | return this.getSection().endsWith("00"); 74 | } 75 | 76 | @JsonProperty("backgroundColor") private String backgroundColor; 77 | 78 | public ClassSection() { } 79 | } 80 | -------------------------------------------------------------------------------- /frontend/src/views/About.vue: -------------------------------------------------------------------------------- 1 | 87 | -------------------------------------------------------------------------------- /frontend/src/store/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Vuex from 'vuex' 3 | import api from '@/components/backend-api.js'; 4 | import mutations from '@/store/mutations.js'; 5 | import getters from '@/store/getters.js'; 6 | 7 | Vue.use(Vuex) 8 | 9 | export default new Vuex.Store({ 10 | state: { 11 | selectedQuarter: null, 12 | selectedDepartment: null, 13 | selectedCollege: null, 14 | selectedSearchFilters: { 15 | selectedSearch: '', 16 | selectedPageSize: 10, 17 | selectedMinUnits: '0', 18 | selectedMaxUnits: '5', 19 | selectedFullClasses: true, 20 | selectedGraduateClasses: true, 21 | selectedRequirement: null, 22 | }, 23 | selectedSession: null, 24 | selectedCourses: [], 25 | selectedCustomEvents: [], 26 | user: {}, 27 | configuration: { 28 | PRIMARY_COLOR: '#003660', 29 | SECONDARY_COLOR: '#FEBC11', 30 | TERTIARY_COLOR: 'purple', 31 | SITE_NAME: 'GauchoCourses', 32 | ORGANIZATION_NAME: 'Data Science UCSB' 33 | } 34 | }, 35 | mutations: { 36 | ...mutations 37 | }, 38 | getters: { 39 | ...getters 40 | }, 41 | actions: { 42 | /** 43 | * Initializes the Vuex store with an optional schedule object. 44 | * 45 | * @param {*} state The Vuex state 46 | * @param {*} schedule An optional schedule object 47 | * 48 | * NOTE: This is used when the user wants to edit one of their schedules. 49 | * The method, initializeStore is used to initialize the app in general. 50 | * These functions should be combined to consider whetherr the scehule is empty and if so, use local storage. 51 | */ 52 | async initializeStoreAsync(context, schedule = null) { 53 | if (schedule) { 54 | 55 | context.commit('setSelectedQuarter', schedule.quarter); 56 | // How is session set? It's not a prop on the schedule 57 | 58 | schedule.customEvents.forEach((entry) => { 59 | context.commit('addCustomEvent', entry); 60 | }); 61 | 62 | context.commit('clearSelectedCourses'); 63 | 64 | const codes = schedule.classes.map(a => a.scheduledEnrollCodes[0]); 65 | var courses = []; 66 | await Promise.all(codes.map(code => api.coursefromEnrollCode(schedule.quarter, code))) 67 | .then(responses => { 68 | courses = responses.map(r => r.data.classes[0]); 69 | }); 70 | 71 | courses.forEach((course) => { 72 | context.commit('addSelectedCourse', course); 73 | }); 74 | 75 | context.state.selectedCourses.forEach((course) => { 76 | const sched = schedule; 77 | const course0 = course; 78 | course.classSections.forEach((section) => { 79 | section.selected = false; 80 | const sectionEnrollCode = section.enrollCode; 81 | const matchingCourse = sched.classes.find(item => item.courseId == course0.courseId); 82 | var matchingEnrollCode = ""; 83 | matchingEnrollCode = matchingCourse.scheduledEnrollCodes.find(enrollCode => enrollCode == sectionEnrollCode); 84 | if (matchingEnrollCode != undefined) { 85 | section.selected = true; 86 | } 87 | }); 88 | }); 89 | } 90 | }, 91 | /** 92 | * Asynchronously sets the user info as part of the state. 93 | * @param {Object} context 94 | */ 95 | async setUserInfoAsync(context) { 96 | context.commit('setUserInfo') 97 | } 98 | }, 99 | modules: { 100 | } 101 | }) 102 | -------------------------------------------------------------------------------- /backend/src/main/java/org/gaucho/courses/domain/core/Event.java: -------------------------------------------------------------------------------- 1 | package org.gaucho.courses.domain.core; 2 | 3 | import com.fasterxml.jackson.annotation.JsonProperty; 4 | import lombok.Data; 5 | import org.gaucho.courses.domain.remote.TimeLocation; 6 | 7 | import java.io.Serializable; 8 | import java.util.ArrayList; 9 | import java.util.List; 10 | import java.util.stream.Collectors; 11 | 12 | /** 13 | * Core parent class for all schedule-able events. Provides methods for finding conflicting events. 14 | */ 15 | @Data 16 | public abstract class Event implements Serializable { 17 | 18 | /** 19 | * 20 | */ 21 | private static final long serialVersionUID = 1L; 22 | 23 | @JsonProperty("timeLocations") 24 | protected List timeLocations = new ArrayList<>(); 25 | 26 | /** 27 | * Returns true if this Event instance conflicts with the given Event. False otherwise. 28 | * @param event Another event object. 29 | * @return boolean. True if the two events conflict, false otherwise. 30 | */ 31 | public boolean conflictsWith(Event event) { 32 | return this.getTimeLocations() 33 | .stream() 34 | .flatMap((TimeLocation a) -> event 35 | .getTimeLocations() 36 | .stream() 37 | .map(a::conflictsWith)) 38 | .anyMatch(a->a); 39 | } 40 | 41 | /** 42 | * Adds a time and place to this Event. 43 | * @param timeLocation a TimeLocation instance. 44 | */ 45 | public void addTimeAndPlace(TimeLocation timeLocation) { 46 | this.timeLocations.add(timeLocation); 47 | } 48 | 49 | // Static Methods 50 | 51 | /** 52 | * Returns true if this event conflicts with any in the given list 53 | * @param events List of events to compare to 54 | * @return true if this conflicts with any in the list, false otherwise. 55 | */ 56 | public boolean conflictsWithAny(List events) { 57 | return events.stream().anyMatch((T event) -> event.conflictsWith(this)); 58 | } 59 | 60 | public boolean conflictsWithAll(List events) { 61 | return events.stream().allMatch((T event) -> event.conflictsWith(this)); 62 | } 63 | 64 | /** 65 | * Returns true if any event in the List conflicts any other Event. (Does not compare events to themselves.) 66 | * @param events A list of Event instances 67 | * @return true if any Event in the List conflicts any other Event 68 | */ 69 | public static boolean eventsHaveConflicts(List events) { 70 | // Need to offset by 1 in inner loop so events are not compared to themselves 71 | for (int j = 0; j < events.size(); j++) 72 | for (int i = j + 1; i < events.size(); i++) { 73 | Event event = events.get(i); 74 | Event event1 = events.get(j); 75 | if (event.conflictsWith(event1)) return true; 76 | } 77 | return false; // No conflicts 78 | } 79 | 80 | /** 81 | * Filters optionalEvents to events that do not conflict with all in requiredEvents 82 | * @param requiredEvents A list of Event instances 83 | * @param optionalEvents A list of Event instances 84 | */ 85 | public static List removeConflicts(final List requiredEvents, 86 | final List optionalEvents) { 87 | return optionalEvents 88 | .stream() 89 | .filter(a -> !a.conflictsWithAny(requiredEvents)) 90 | .collect(Collectors.toList()); 91 | } 92 | 93 | } 94 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ### Intellij ### 2 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm 3 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 4 | 5 | .idea/ 6 | 7 | # User-specific stuff 8 | .idea/**/workspace.xml 9 | .idea/**/tasks.xml 10 | .idea/**/usage.statistics.xml 11 | .idea/**/dictionaries 12 | .idea/**/shelf 13 | 14 | # Generated files 15 | .idea/**/contentModel.xml 16 | 17 | # Sensitive or high-churn files 18 | .idea/**/dataSources/ 19 | .idea/**/dataSources.ids 20 | .idea/**/dataSources.local.xml 21 | .idea/**/sqlDataSources.xml 22 | .idea/**/dynamic.xml 23 | .idea/**/uiDesigner.xml 24 | .idea/**/dbnavigator.xml 25 | 26 | # Gradle 27 | .idea/**/gradle.xml 28 | .idea/**/libraries 29 | 30 | # Gradle and Maven with auto-import 31 | # When using Gradle or Maven with auto-import, you should exclude module files, 32 | # since they will be recreated, and may cause churn. Uncomment if using 33 | # auto-import. 34 | # .idea/modules.xml 35 | # .idea/*.iml 36 | # .idea/modules 37 | # *.iml 38 | # *.ipr 39 | 40 | # CMake 41 | cmake-build-*/ 42 | 43 | # Mongo Explorer plugin 44 | .idea/**/mongoSettings.xml 45 | 46 | # File-based project format 47 | *.iws 48 | 49 | # IntelliJ 50 | out/ 51 | 52 | # mpeltonen/sbt-idea plugin 53 | .idea_modules/ 54 | 55 | # JIRA plugin 56 | atlassian-ide-plugin.xml 57 | 58 | # Cursive Clojure plugin 59 | .idea/replstate.xml 60 | 61 | # Crashlytics plugin (for Android Studio and IntelliJ) 62 | com_crashlytics_export_strings.xml 63 | crashlytics.properties 64 | crashlytics-build.properties 65 | fabric.properties 66 | 67 | # Editor-based Rest Client 68 | .idea/httpRequests 69 | 70 | # Android studio 3.1+ serialized cache file 71 | .idea/caches/build_file_checksums.ser 72 | 73 | ### Intellij Patch ### 74 | # Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721 75 | 76 | *.iml 77 | # modules.xml 78 | # .idea/misc.xml 79 | # *.ipr 80 | 81 | # Sonarlint plugin 82 | .idea/**/sonarlint/ 83 | 84 | # SonarQube Plugin 85 | .idea/**/sonarIssues.xml 86 | 87 | # Markdown Navigator plugin 88 | .idea/**/markdown-navigator.xml 89 | .idea/**/markdown-navigator/ 90 | 91 | # End of https://www.gitignore.io/api/intellij 92 | 93 | 94 | 95 | 96 | 97 | 98 | ### Other editors ### 99 | .project 100 | .classpath 101 | .factorypath 102 | .settings/ 103 | ### End other editors ### 104 | 105 | 106 | 107 | ### Mac Files ### 108 | .DS_Store 109 | ### end Mac Files ### 110 | 111 | 112 | 113 | 114 | 115 | ### Frontend files ### 116 | frontend/dist/ 117 | frontend/node/ 118 | frontend/node_modules/ 119 | frontend/npm-debug.log 120 | frontend/target/ 121 | frontend/test-output 122 | frontend/tests/e2e/reports 123 | frontend/tests/unit/coverage 124 | frontend/yarn-error.log 125 | frontend/yarn.lock 126 | ### end Frontend files ### 127 | 128 | 129 | 130 | ### Java artifacts and files ### 131 | 132 | # Target directory 133 | */target/** 134 | 135 | # Compiled class file 136 | *.class 137 | 138 | # Log file 139 | *.log 140 | 141 | # BlueJ files 142 | *.ctxt 143 | 144 | # Mobile Tools for Java (J2ME) 145 | .mtj.tmp/ 146 | 147 | # Package Files # 148 | *.jar 149 | *.war 150 | *.nar 151 | *.ear 152 | *.zip 153 | *.tar.gz 154 | *.rar 155 | 156 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 157 | hs_err_pid* 158 | 159 | 160 | # Do not check in compiled frontend code 161 | backend/src/main/resources/public/ 162 | 163 | # NPM config file 164 | .npmrc 165 | 166 | # Ignore any locally-generated ARM templates from the Bicep files 167 | infrastructure.json -------------------------------------------------------------------------------- /frontend/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | 5 | frontend 6 | 7 | 8 | org.gaucho 9 | courses 10 | 0.0.1-SNAPSHOT 11 | 12 | 13 | 14 | UTF-8 15 | UTF-8 16 | 1.8.0 17 | 18 | 19 | 20 | 21 | 22 | com.github.eirslett 23 | frontend-maven-plugin 24 | ${frontend-maven-plugin.version} 25 | 26 | 27 | 28 | install node and npm 29 | 30 | install-node-and-npm 31 | 32 | 33 | v18.0.0 34 | 35 | 36 | 37 | 38 | npm install 39 | 40 | npm 41 | 42 | 43 | generate-resources 44 | 45 | 46 | install 47 | 48 | 49 | 50 | 51 | npm run build 52 | 53 | npm 54 | 55 | 56 | run build 57 | 58 | 59 | 60 | 61 | npm run test:unit 62 | 63 | npm 64 | 65 | test 66 | 67 | run test:unit 68 | 69 | 70 | 71 | 83 | 84 | 85 | 86 | 87 | 88 | -------------------------------------------------------------------------------- /frontend/tests/unit/components/SelectedEvents.spec.js: -------------------------------------------------------------------------------- 1 | import { createLocalVue, mount } from '@vue/test-utils'; 2 | import SelectedEvents from '@/components/events/SelectedEvents.vue'; 3 | import BootstrapVue from 'bootstrap-vue'; 4 | import { customEvent, courses } from '../../testing-objects'; 5 | import Vuex from "vuex" 6 | import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome'; 7 | import "jest-canvas-mock"; 8 | 9 | const localVue = createLocalVue(); 10 | localVue.use(BootstrapVue); 11 | localVue.use(Vuex); 12 | localVue.component('font-awesome-icon', FontAwesomeIcon); 13 | 14 | const factory = (state, getters) => { 15 | return mount(SelectedEvents, { 16 | localVue, 17 | mocks: { 18 | $store: { 19 | state: state, 20 | getters: getters, 21 | commit: jest.fn() 22 | }, 23 | $eventHub: { 24 | $on: jest.fn(), 25 | $off: jest.fn(), 26 | $emit: jest.fn(), 27 | } 28 | } 29 | }) 30 | } 31 | 32 | describe('SelectedEvents.vue', () => { 33 | 34 | describe('when there are no custom events or courses in the store', () => { 35 | const noEvents = { 36 | selectedCourses: [], 37 | selectedCustomEvents: [] 38 | }; 39 | 40 | const getters = { 41 | selectedClassSections: [] 42 | } 43 | 44 | it('it renders placeholder text', () => { 45 | const wrapper = factory(noEvents, getters); 46 | expect(wrapper.find('.no-events-placeholder').exists()).toBe(true); 47 | }); 48 | }); 49 | 50 | describe('when the store has custom events and courses', () => { 51 | const events = { 52 | selectedCourses: courses, 53 | selectedCustomEvents: [customEvent] 54 | } 55 | 56 | const getters = { 57 | selectedClassSections: events.selectedCourses.flatMap(c => c.classSections) 58 | } 59 | 60 | const wrapper = factory(events, getters); 61 | 62 | it('the events are rendered with their names', () => { 63 | const courses = wrapper.findAll('.selected-course'); 64 | expect(courses.length).toBe(2); 65 | expect(courses.at(0).find('.event-name').text().substring(0,7)).toBe('DUM 200') 66 | }); 67 | 68 | it('the courses are rendered with their titles', () => { 69 | const customEvents = wrapper.findAll('.custom-event'); 70 | expect(customEvents.length).toBe(1); 71 | expect(customEvents.at(0).find('.event-name').text()).toBe('testEvent'); 72 | }); 73 | }); 74 | 75 | describe('when a user clicks the button to remove a custom event or course', () => { 76 | const events = { 77 | selectedCourses: courses, 78 | selectedCustomEvents: [customEvent] 79 | } 80 | 81 | const getters = { 82 | selectedClassSections: events.selectedCourses.flatMap(c => c.classSections) 83 | } 84 | 85 | const wrapper = factory(events, getters); 86 | 87 | it('commits a mutation to remove the custom event', () => { 88 | wrapper.find('.custom-event').find('.remove-event').trigger('click'); 89 | //expect(wrapper.mocks.$store.commit).toHaveBeenCalled(); 90 | }); 91 | 92 | it('commits a mutation to remove the course', () => { 93 | 94 | }); 95 | }); 96 | 97 | describe('when a user clicks the button to edit a course or custom event', () => { 98 | it('emits an event to edit the custom event', () => { 99 | 100 | }); 101 | 102 | it('emits an event to edit the course', () => { 103 | 104 | }); 105 | }); 106 | 107 | describe('when a user clicks the button to create a custom event', () => { 108 | it('emits an event to create a custom event', () => { 109 | 110 | }); 111 | }); 112 | }) 113 | -------------------------------------------------------------------------------- /backend/src/main/java/org/gaucho/courses/controller/Gateway.java: -------------------------------------------------------------------------------- 1 | package org.gaucho.courses.controller; 2 | 3 | import com.fasterxml.jackson.annotation.JsonProperty; 4 | import lombok.Data; 5 | import lombok.NoArgsConstructor; 6 | import org.gaucho.courses.controller.service.GatewayService; 7 | import org.gaucho.courses.domain.remote.Department; 8 | import org.gaucho.courses.domain.remote.Quarter; 9 | import org.gaucho.courses.domain.remote.Class; 10 | import org.springframework.beans.factory.annotation.Autowired; 11 | import org.springframework.http.*; 12 | import org.springframework.stereotype.Controller; 13 | import org.springframework.web.bind.annotation.RequestMapping; 14 | import org.springframework.web.bind.annotation.RequestParam; 15 | 16 | import javax.servlet.http.HttpServletRequest; 17 | import java.util.List; 18 | import java.io.Serializable; 19 | import java.util.Map; 20 | import java.util.concurrent.TimeUnit; 21 | 22 | @Controller 23 | @RequestMapping("remote/") 24 | public class Gateway { 25 | 26 | @Autowired 27 | private GatewayService service; 28 | 29 | private final CacheControl cacheControl = CacheControl 30 | .maxAge(30, TimeUnit.MINUTES) 31 | .noTransform() 32 | .cachePublic(); 33 | 34 | @RequestMapping("academics/quartercalendar/v1/quarters/**") 35 | public ResponseEntity getQuarter(@RequestParam Map allQueryParams, HttpServletRequest request) { 36 | final Quarter[] quarters = service.getQuarter(allQueryParams, request); 37 | 38 | if (quarters != null) { 39 | return ResponseEntity 40 | .ok() 41 | .cacheControl(cacheControl) 42 | .body(quarters); 43 | } else { 44 | return ResponseEntity 45 | .status(HttpStatus.INTERNAL_SERVER_ERROR) 46 | .body(null); 47 | } 48 | } 49 | 50 | @RequestMapping("academics/curriculums/v1/classes/**") 51 | public ResponseEntity getClassSections(@RequestParam Map allQueryParams, HttpServletRequest request) { 52 | 53 | PaginatedClasses classes = service.getClassSections(allQueryParams, request); 54 | 55 | // Search by title if courseId returned null 56 | if (classes == null && allQueryParams.containsKey("courseId")) { 57 | allQueryParams.put("title", allQueryParams.get("courseId")); 58 | allQueryParams.remove("courseId"); 59 | classes = service.getClassSections(allQueryParams, request); 60 | } 61 | 62 | if (classes != null) { 63 | return ResponseEntity 64 | .ok() 65 | .cacheControl(cacheControl) 66 | .body(classes); 67 | } else { 68 | return ResponseEntity 69 | .status(HttpStatus.INTERNAL_SERVER_ERROR) 70 | .body(null); 71 | } 72 | } 73 | 74 | @RequestMapping("students/lookups/v1/departments/**") 75 | public ResponseEntity getDepartments(@RequestParam Map allQueryParams, HttpServletRequest request) { 76 | final Department[] departments = service.getDepartments(allQueryParams, request); 77 | 78 | if (departments != null) { 79 | return ResponseEntity 80 | .ok() 81 | .cacheControl(cacheControl) 82 | .body(departments); 83 | } else { 84 | return ResponseEntity 85 | .status(HttpStatus.INTERNAL_SERVER_ERROR) 86 | .body(null); 87 | } 88 | } 89 | 90 | @Data 91 | @NoArgsConstructor 92 | public static class PaginatedClasses implements Serializable { 93 | 94 | private static final long serialVersionUID = 1L; 95 | 96 | @JsonProperty("pageNumber") private Integer pageNumber; 97 | @JsonProperty("pageSize") private Integer pageSize; 98 | @JsonProperty("total") private Integer total; 99 | @JsonProperty("classes") private List classes; 100 | } 101 | 102 | } -------------------------------------------------------------------------------- /frontend/src/App.vue: -------------------------------------------------------------------------------- 1 | 80 | 81 | 94 | 95 | 140 | -------------------------------------------------------------------------------- /frontend/src/assets/google_signin_buttons/web/vector/btn_google_light_normal_ios.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | btn_google_light_normal_ios 5 | Created with Sketch. 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /frontend/src/assets/google_signin_buttons/web/vector/btn_google_light_pressed_ios.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | btn_google_light_pressed_ios 5 | Created with Sketch. 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /backend/src/main/java/org/gaucho/courses/domain/remote/Class.java: -------------------------------------------------------------------------------- 1 | package org.gaucho.courses.domain.remote; 2 | 3 | import com.fasterxml.jackson.annotation.JsonIgnore; 4 | 5 | import com.fasterxml.jackson.annotation.JsonProperty; 6 | import com.fasterxml.jackson.annotation.JsonPropertyOrder; 7 | import lombok.Data; 8 | import lombok.EqualsAndHashCode; 9 | 10 | import java.io.Serializable; 11 | import java.util.HashMap; 12 | import java.util.List; 13 | import java.util.Map; 14 | 15 | /** 16 | * Represents a course in a given quarter. One "Class" maps to one or more Lectures, and one Lecture maps to 17 | * zero or more Sections. 18 | */ 19 | @JsonPropertyOrder({ 20 | "quarter", 21 | "courseId", 22 | "title", 23 | "contactHours", 24 | "description", 25 | "college", 26 | "objLevelCode", 27 | "subjectArea", 28 | "unitsFixed", 29 | "unitsVariableHigh", 30 | "unitsVariableLow", 31 | "delayedSectioning", 32 | "inProgressCourse", 33 | "gradingOption", 34 | "instructionType", 35 | "onLineCourse", 36 | "deptCode", 37 | "generalEducation", 38 | "classSections" 39 | }) 40 | @Data 41 | @EqualsAndHashCode 42 | public class Class implements Serializable { 43 | 44 | private static final long serialVersionUID = 1L; 45 | 46 | @JsonProperty("quarter") private String quarter; 47 | @JsonProperty("courseId") private String courseId; 48 | @JsonProperty("title") private String title; 49 | @JsonProperty("contactHours") private Integer contactHours; 50 | @JsonProperty("description") private String description; 51 | @JsonProperty("college") private String college; 52 | @JsonProperty("objLevelCode") private String objLevelCode; 53 | @JsonProperty("subjectArea") private String subjectArea; 54 | @JsonProperty("unitsFixed") private Integer unitsFixed; 55 | @JsonProperty("unitsVariableHigh") private Integer unitsVariableHigh; 56 | @JsonProperty("unitsVariableLow") private Integer unitsVariableLow; 57 | @JsonProperty("delayedSectioning") private String delayedSectioning; 58 | @JsonProperty("inProgressCourse") private String inProgressCourse; 59 | @JsonProperty("gradingOption") private String gradingOption; 60 | @JsonProperty("instructionType") private String instructionType; 61 | @JsonProperty("onLineCourse") private Boolean onLineCourse; 62 | @JsonProperty("deptCode") private String deptCode; 63 | @JsonProperty("generalEducation") private List generalEducation = null; 64 | @JsonProperty("classSections") private List classSections = null; 65 | 66 | @JsonIgnore 67 | private Map additionalProperties = new HashMap(); 68 | 69 | // Constructors 70 | 71 | /** 72 | * JPA requires a default, no-args constructor. 73 | */ 74 | public Class() {} 75 | 76 | /** 77 | * Utility method to getCombination the full course name. (Prefix, number, suffix in one string). 78 | */ 79 | public String getFullCourseNumber() { 80 | return getDeptCode()+getCourseNumberPrefix()+getCourseNumber()+getCourseNumberSuffix(); 81 | } 82 | 83 | /** 84 | * Helper method to getCombination the courseId. Removes whitespace. From the API docs: 85 | * 13 character course Id with format SSSSSPPPNNNNUUU , Fist 5 char is course subject, Next 3 char is course prefix then next 3 char is course number and last 2 char is course suffix. 86 | * For example 'ES 1- 16B ' can be broken down in subject 'ES ' prefix '1- ' number ' 16' suffix 'B 87 | * @param start Start index (inclusive) 88 | * @param end End index (inclusive) 89 | * @return The split string with whitespace removed 90 | */ 91 | private String getCourseIdSubString(int start, int end) { 92 | String substring = getCourseId().substring(start, end); 93 | return substring.replaceAll(" ", ""); 94 | } 95 | 96 | private String getCourseNumberPrefix() { 97 | return getCourseIdSubString(5, 8); 98 | } 99 | 100 | private String getCourseNumber() { 101 | return getCourseIdSubString(8,11); 102 | } 103 | 104 | private String getCourseNumberSuffix() { 105 | return getCourseIdSubString(11, 13); 106 | } 107 | 108 | public String toString() { 109 | return "Class "+this.getFullCourseNumber()+" with "+this.getClassSections().size()+" class sections."; 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /frontend/src/assets/google_signin_buttons/web/vector/btn_google_light_focus_ios.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | btn_google_light_focus_ios 5 | Created with Sketch. 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /frontend/src/components/util/util-methods.js: -------------------------------------------------------------------------------- 1 | import dayjs from 'dayjs'; 2 | import api from '@/components/backend-api.js'; 3 | 4 | 5 | /** 6 | * Returns the value of the specified cookie. If the cookie cannot be found, returns null; 7 | * @param {string} key The key of the cookie 8 | */ 9 | export function getCookieValue(key) { 10 | const cookie = document.cookie 11 | .split('; ') 12 | .find(cookie => cookie.startsWith(key)); 13 | 14 | return cookie ? cookie.split('=')[1] : null; 15 | } 16 | 17 | 18 | /** 19 | * Parses the URL to get the slot name. 20 | */ 21 | export function getSlotNameFromURL() { 22 | const regex = new RegExp('-([a-zA-z_0-9]+).azurewebsites.net'); // This regex isn't good. The hostname can have dashes, and the slot name can also have dashes. 23 | const slot = regex.exec(document.URL); 24 | return slot ? slot[1] : null; // Get the first capture group 25 | } 26 | 27 | 28 | /** 29 | * Converts HH:mm:ss format to h:mm A 30 | * @param {string} timestring String with the time in HH:mm:ss format 31 | * @returns {string} string with format of h:mm A 32 | */ 33 | export function formatTime(timeString) { 34 | return dayjs(timeString,"HH:mm:ss").format("h:mm A"); 35 | } 36 | 37 | 38 | /** 39 | * Calls the API to get the latest quarters. 40 | */ 41 | export function getQuarters(){ 42 | var quarters = []; 43 | var today = new Date(); 44 | var dd = String(today.getDate()).padStart(2, "0"); 45 | 46 | var yyyy = today.getFullYear(); 47 | var mm = String(today.getMonth() + 1).padStart(2, "0"); // months are 0-indexed 48 | var thisquarter = `${yyyy}${mm}${dd}`; 49 | 50 | var future = new Date(today); 51 | 52 | future.setMonth(today.getMonth() + 2); // 2 months from today 53 | yyyy = future.getFullYear(); 54 | mm = String(future.getMonth() + 1).padStart(2, '0'); // months are 0-indexed 55 | var nexthalfquarter = `${yyyy}${mm}${dd}`; 56 | 57 | future.setMonth(future.getMonth() + 2); // 4 months from today 58 | yyyy = future.getFullYear(); 59 | mm = String(future.getMonth() + 1).padStart(2, '0'); // months are 0-indexed 60 | var nextquarter = `${yyyy}${mm}${dd}`; 61 | 62 | // future.setMonth(future.getMonth() + 2); // 6 months from today 63 | // yyyy = future.getFullYear(); 64 | // mm = String(future.getMonth() + 1).padStart(2, '0'); // months are 0-indexed 65 | // var nextnextquarter = `${yyyy}${mm}${dd}`; 66 | 67 | //TODO: This roundabout method can be more efficent if we pull from UCSB's quartercalendar to see the exact dates 68 | 69 | var seen = new Set(); 70 | // Get the current and next quarters. Select the next quarter. 71 | Promise.all([api.quarters(thisquarter), api.quarters(nexthalfquarter), api.quarters(nextquarter)]) 72 | .then(responses => { 73 | for (let i = 0; i < responses.length; i++) { // Remove duplicates 74 | if (!seen.has(responses[i].data[0].category)) { 75 | seen.add(responses[i].data[0].category); 76 | quarters.push(responses[i].data[0]); 77 | } 78 | } 79 | }) 80 | .catch(err => { 81 | err; 82 | // ToDo. This is a critical code path, so if this fails then we should render 83 | // a critical error AND LOG IT SO THAT WE CAN KNOW HOW OFTEN THIS HAPPENS. 84 | }); 85 | return quarters; 86 | } 87 | 88 | 89 | 90 | 91 | /** 92 | * Used as a default method parameter value to assert that the parameter cannot be empty. 93 | */ 94 | const checkUndefined = () => { 95 | throw new Error('Method argument cannot be undefined'); 96 | }; 97 | 98 | 99 | /** 100 | * Group items from an array together by some criteria or value. 101 | * (c) 2019 Tom Bremmer (https://tbremer.com/) and Chris Ferdinandi (https://gomakethings.com), MIT License 102 | * https://gomakethings.com/a-vanilla-js-equivalent-of-lodashs-groupby-method/ 103 | * @param {Array} arr The array to group items from 104 | * @param {String|Function} criteria The criteria to group by 105 | * @return {Object} The grouped object 106 | */ 107 | export function groupBy(arr = checkUndefined(), criteria) { 108 | return arr.reduce(function (obj, item) { 109 | 110 | // Check if the criteria is a function to run on the item or a property of it 111 | var key = typeof criteria === 'function' ? criteria(item) : item[criteria]; 112 | 113 | // If the key doesn't exist yet, create it 114 | if (!Object.prototype.hasOwnProperty.call(obj, key)) { 115 | obj[key] = []; 116 | } 117 | 118 | // Push the value to the object 119 | obj[key].push(item); 120 | 121 | // Return the object to the next item in the loop 122 | return obj; 123 | 124 | }, {}); 125 | } 126 | -------------------------------------------------------------------------------- /frontend/src/components/events/ListItems/CourseListItem.vue: -------------------------------------------------------------------------------- 1 | 38 | 39 | 126 | 127 | -------------------------------------------------------------------------------- /frontend/src/assets/google_signin_buttons/web/vector/btn_google_dark_normal_ios.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | btn_google_dark_normal_ios 5 | Created with Sketch. 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | --------------------------------------------------------------------------------