diff --git a/scigym/config/production.py b/scigym/config/production.py index c1635a0..7e851e4 100755 --- a/scigym/config/production.py +++ b/scigym/config/production.py @@ -14,15 +14,15 @@ class Production(Common): # https://docs.djangoproject.com/en/2.0/howto/static-files/ # http://django-storages.readthedocs.org/en/latest/index.html INSTALLED_APPS += ('storages',) - DEFAULT_FILE_STORAGE = 'whitenoise.storage.CompressedManifestStaticFilesStorage' - STATICFILES_STORAGE = 'whitenoise.storage.CompressedManifestStaticFilesStorage' + DEFAULT_FILE_STORAGE = 'storages.backends.s3boto3.S3Boto3Storage' + STATICFILES_STORAGE = 'storages.backends.s3boto3.S3Boto3Storage' AWS_ACCESS_KEY_ID = os.getenv('DJANGO_AWS_ACCESS_KEY_ID') AWS_SECRET_ACCESS_KEY = os.getenv('DJANGO_AWS_SECRET_ACCESS_KEY') AWS_STORAGE_BUCKET_NAME = os.getenv('DJANGO_AWS_STORAGE_BUCKET_NAME') AWS_DEFAULT_ACL = 'public-read' - AWS_AUTO_CREATE_BUCKET = True + AWS_AUTO_CREATE_BUCKET = False AWS_QUERYSTRING_AUTH = False - MEDIA_URL = f'https://s3.amazonaws.com/{AWS_STORAGE_BUCKET_NAME}/' + MEDIA_URL = f'https://s3.amazonaws.com/{AWS_STORAGE_BUCKET_NAME}/user_images/' # https://developers.google.com/web/fundamentals/performance/optimizing-content-efficiency/http-caching#cache-control # Response can be cached by browser and any intermediary caches (i.e. it is "public") for up to 1 day diff --git a/static/index.html b/static/index.html index 1770980..0bf3932 100644 --- a/static/index.html +++ b/static/index.html @@ -1 +1 @@ -SciGym
\ No newline at end of file +SciGym
\ No newline at end of file diff --git a/static/js/main.5c5421f7.chunk.js b/static/js/main.5c5421f7.chunk.js new file mode 100644 index 0000000..27863da --- /dev/null +++ b/static/js/main.5c5421f7.chunk.js @@ -0,0 +1,2 @@ +(window.webpackJsonp=window.webpackJsonp||[]).push([[0],{256:function(e,t,a){e.exports=a(465)},265:function(e,t,a){},451:function(e,t,a){e.exports=a.p+"static/media/play.a860c2a7.md"},452:function(e,t,a){e.exports=a.p+"static/media/contribute.9b42cb7f.md"},465:function(e,t,a){"use strict";a.r(t);var n=a(0),r=a.n(n),o=a(45),i=a.n(o),s=a(14),c=a(15),l=a(470),u=a(195),E=(a(265),a(24)),m=a.n(E),d={CLEAR_ACCESS_TOKEN:"CLEAR_ACCESS_TOKEN",REHYDRATE_AUTH_TOKEN:"REHYDRATE_AUTH_TOKEN",GET_API_CONFIG:"GET_API_CONFIG",GET_API_CONFIG_SUCCESS:"GET_API_CONFIG_SUCCESS",GET_API_CONFIG_FAILURE:"GET_API_CONFIG_FAILURE",GET_API_STATUS:"GET_API_STATUS",GET_API_STATUS_SUCCESS:"GET_API_STATUS_SUCCESS",GET_API_STATUS_FAILURE:"GET_API_STATUS_FAILURE",LOGIN_USER_GITHUB_OAUTH:"LOGIN_USER_GITHUB_OAUTH",LOGIN_USER_GITHUB_OAUTH_SUCCESS:"LOGIN_USER_GITHUB_OAUTH_SUCCESS",LOGIN_USER_GITHUB_OAUTH_FAILURE:"LOGIN_USER_GITHUB_OAUTH_FAILURE",LOGOUT_USER:"LOGOUT_USER",LOGOUT_USER_SUCCESS:"LOGOUT_USER_SUCCESS",LOGOUT_USER_FAILURE:"LOGOUT_USER_FAILURE",REFRESH_AUTH_TOKEN:"REFRESH_AUTH_TOKEN",REFRESH_AUTH_TOKEN_SUCCESS:"REFRESH_AUTH_TOKEN_SUCCESS",REFRESH_AUTH_TOKEN_FAILURE:"REFRESH_AUTH_TOKEN_FAILURE",GET_USER_PROFILE:"GET_USER_PROFILE",GET_USER_PROFILE_SUCCESS:"GET_USER_PROFILE_SUCCESS",GET_USER_PROFILE_FAILURE:"GET_USER_PROFILE_FAILURE",UPDATE_USER_PROFILE:"UPDATE_USER_PROFILE",UPDATE_USER_PROFILE_SUCCESS:"UPDATE_USER_PROFILE_SUCCESS",UPDATE_USER_PROFILE_FAILURE:"UPDATE_USER_PROFILE_FAILURE",DELETE_USER:"DELETE_USER",DELETE_USER_SUCCESS:"DELETE_USER_SUCCESS",DELETE_USER_FAILURE:"DELETE_USER_FAILURE",CREATE_ENVIRONMENT:"CREATE_ENVIRONMENT",CREATE_ENVIRONMENT_SUCCESS:"CREATE_ENVIRONMENT_SUCCESS",CREATE_ENVIRONMENT_FAILURE:"CREATE_ENVIRONMENT_FAILURE",EDIT_ENVIRONMENT:"EDIT_ENVIRONMENT",EDIT_ENVIRONMENT_SUCCESS:"EDIT_ENVIRONMENT_SUCCESS",EDIT_ENVIRONMENT_FAILURE:"EDIT_ENVIRONMENT_FAILURE",GET_ENVIRONMENTS_LIST:"GET_ENVIRONMENTS_LIST",GET_ENVIRONMENTS_LIST_SUCCESS:"GET_ENVIRONMENTS_LIST_SUCCESS",GET_ENVIRONMENTS_LIST_FAILURE:"GET_ENVIRONMENTS_LIST_FAILURE",DELETE_ENVIRONMENT:"DELETE_ENVIRONMENT",DELETE_ENVIRONMENT_SUCCESS:"DELETE_ENVIRONMENT_SUCCESS",DELETE_ENVIRONMENT_FAILURE:"DELETE_ENVIRONMENT_FAILURE",RESET_ENVIRONMENTS_PROPS:"RESET_ENVIRONMENTS_PROPS",RESET_ENVIRONMENTS_ERRORS:"RESET_ENVIRONMENTS_ERRORS",SEARCH_ENVIRONMENTS:"SEARCH_ENVIRONMENTS",SEARCH_ENVIRONMENTS_SUCCESS:"SEARCH_ENVIRONMENTS_SUCCESS",SEARCH_ENVIRONMENTS_FAILURE:"SEARCH_ENVIRONMENTS_FAILURE",SEARCH_ENVIRONMENTS_RESET:"SEARCH_ENVIRONMENTS_RESET",CATEGORIZE_ENVIRONMENTS:"CATEGORIZE_ENVIRONMENTS",CATEGORIZE_ENVIRONMENTS_SUCCESS:"CATEGORIZE_ENVIRONMENTS_SUCCESS",CATEGORIZE_ENVIRONMENTS_FAILURE:"CATEGORIZE_ENVIRONMENTS_FAILURE",CATEGORIZE_ENVIRONMENTS_RESET:"CATEGORIZE_ENVIRONMENTS_RESET",GET_REPOSITORIES:"GET_REPOSITORIES",GET_REPOSITORIES_SUCCESS:"GET_REPOSITORIES_SUCCESS",GET_REPOSITORIES_FAILURE:"GET_REPOSITORIES_FAILURE",GET_TOPICS:"GET_TOPICS",GET_TOPICS_SUCCESS:"GET_TOPICS_SUCCESS",GET_TOPICS_FAILURE:"GET_TOPICS_FAILURE",CREATE_IMAGE:"CREATE_IMAGE",CREATE_IMAGE_SUCCESS:"CREATE_IMAGE_SUCCESS",CREATE_IMAGE_FAILURE:"CREATE_IMAGE_FAILURE",GET_USER_IMAGES_LIST:"GET_USER_IMAGES_LIST",GET_USER_IMAGES_LIST_SUCCESS:"GET_USER_IMAGES_LIST_SUCCESS",GET_USER_IMAGES_LIST_FAILURE:"GET_USER_IMAGES_LIST_FAILURE",GET_IMAGE_CONFIG_LIST:"GET_IMAGE_CONFIG_LIST",GET_IMAGE_CONFIG_LIST_SUCCESS:"GET_IMAGE_CONFIG_LIST_SUCCESS",GET_IMAGE_CONFIG_LIST_FAILURE:"GET_IMAGE_CONFIG_LIST_FAILURE",DELETE_IMAGE:"DELETE_IMAGE",DELETE_IMAGE_SUCCESS:"DELETE_IMAGE_SUCCESS",DELETE_IMAGE_FAILURE:"DELETE_IMAGE_FAILURE",RESET_IMAGE_PROPS:"RESET_IMAGE_PROPS",GET_CONTRIBUTORS_LIST:"GET_CONTRIBUTORS_LIST",GET_CONTRIBUTORS_LIST_SUCCESS:"GET_CONTRIBUTORS_LIST_SUCCESS",GET_CONTRIBUTORS_LIST_FAILURE:"GET_CONTRIBUTORS_LIST_FAILURE",GET_PROJECTAUTHORS_LIST:"GET_PROJECTAUTHORS_LIST",GET_PROJECTAUTHORS_LIST_SUCCESS:"GET_PROJECTAUTHORS_LIST_SUCCESS",GET_PROJECTAUTHORS_LIST_FAILURE:"GET_PROJECTAUTHORS_LIST_FAILURE",GET_USER_REPOSITORIES_LIST:"GET_USER_REPOSITORIES_LIST",GET_USER_REPOSITORIES_LIST_SUCCESS:"GET_USER_REPOSITORIES_LIST_SUCCESS",GET_USER_REPOSITORIES_LIST_FAILURE:"GET_USER_REPOSITORIES_LIST_FAILURE",FIND_GYM_REPOS:"FIND_GYM_REPOS",FIND_GYM_REPOS_SUCCESS:"FIND_GYM_REPOS_SUCCESS",FIND_GYM_REPOS_FAILURE:"FIND_GYM_REPOS_FAILURE",CREATE_MESSAGEBOARD:"CREATE_MESSAGEBOARD",CREATE_MESSAGEBOARD_SUCCESS:"CREATE_MESSAGEBOARD_SUCCESS",CREATE_MESSAGEBOARD_FAILURE:"CREATE_MESSAGEBOARD_FAILURE",EDIT_MESSAGEBOARD:"EDIT_MESSAGEBOARD",EDIT_MESSAGEBOARD_SUCCESS:"EDIT_MESSAGEBOARD_SUCCESS",EDIT_MESSAGEBOARD_FAILURE:"EDIT_MESSAGEBOARD_FAILURE",GET_MESSAGEBOARDS_LIST:"GET_MESSAGEBOARDS_LIST",GET_MESSAGEBOARDS_LIST_SUCCESS:"GET_MESSAGEBOARDS_LIST_SUCCESS",GET_MESSAGEBOARDS_LIST_FAILURE:"GET_MESSAGEBOARDS_LIST_FAILURE",DELETE_MESSAGEBOARD:"DELETE_MESSAGEBOARD",DELETE_MESSAGEBOARD_SUCCESS:"DELETE_MESSAGEBOARD_SUCCESS",DELETE_MESSAGEBOARD_FAILURE:"DELETE_MESSAGEBOARD_FAILURE",RESET_MESSAGEBOARDS_PROPS:"RESET_MESSAGEBOARDS_PROPS",RESET_MESSAGEBOARDS_ERRORS:"RESET_MESSAGEBOARDS_ERRORS",COUNT_COMMENTS:"COUNT_COMMENTS",COUNT_COMMENTS_SUCCESS:"COUNT_COMMENTS_SUCCESS",COUNT_COMMENTS_FAILURE:"COUNT_COMMENTS_FAILURE",CREATE_COMMENT:"CREATE_COMMENT",CREATE_COMMENT_SUCCESS:"CREATE_COMMENT_SUCCESS",CREATE_COMMENT_FAILURE:"CREATE_COMMENT_FAILURE",EDIT_COMMENT:"EDIT_COMMENT",EDIT_COMMENT_SUCCESS:"EDIT_COMMENT_SUCCESS",EDIT_COMMENT_FAILURE:"EDIT_COMMENT_FAILURE",GET_COMMENTS_LIST:"GET_COMMENTS_LIST",GET_COMMENTS_LIST_SUCCESS:"GET_COMMENTS_LIST_SUCCESS",GET_COMMENTS_LIST_FAILURE:"GET_COMMENTS_LIST_FAILURE",DELETE_COMMENT:"DELETE_COMMENT",DELETE_COMMENT_SUCCESS:"DELETE_COMMENT_SUCCESS",DELETE_COMMENT_FAILURE:"DELETE_COMMENT_FAILURE",RESET_COMMENTS_PROPS:"RESET_COMMENTS_PROPS",RESET_COMMENTS_ERRORS:"RESET_COMMENTS_ERRORS",RESET_COMMENTS:"RESET_COMMENTS"},p=a(196),h="http://localhost:8000",g="",S="https://scigym.s3.eu-central-1.amazonaws.com";function f(){var e=window.location.hostname;return Object(p.includes)(e,"localhost")}var b={REFRESH_TOKEN:"refreshToken",ACCESS_TOKEN:"accessToken",STATIC_URL:f()?"".concat(h,"/static"):S,MEDIA_URL:f()?h:"",SCIGYM_LOGO:"/icons/scigym-logo.png",GITHUB_LOGO:"/icons/github-circle.png",TWITTER_LOGO:"/icons/twitter-logo.png",BANNER:"/images/ai_photonics_banner.jpg",RL_PARADIGM:"/images/RLParadigm.gif",SCIENCE_API:"/images/APIforScience.png",SCIENCE_RL:"/images/RLforScience.png",SCIENCE_CPU:"/images/CPUforScience.png"};var y,O=function(){return function(e){return function(t){if(t.type===d.LOGIN_USER_GITHUB_OAUTH_SUCCESS||t.type===d.REFRESH_AUTH_TOKEN_SUCCESS){var a=t.payload;!function(e,t){localStorage.setItem(b.REFRESH_TOKEN,t),m.a.defaults.headers.common.Authorization="Bearer ".concat(e)}(a.accessToken,a.refreshToken)}t.type!==d.LOGOUT_USER_SUCCESS&&t.type!==d.DELETE_USER_SUCCESS||(localStorage.removeItem(b.REFRESH_TOKEN),m.a.defaults.headers.common={}),e(t)}}},v=a(5),_=a(127),T=a.n(_),C={loaded:!1,githubRandomState:T()(),githubClientId:void 0,githubCallbackUrl:void 0},R={accessToken:void 0,expiresIn:void 0,firstName:void 0,id:void 0,lastName:void 0,refreshToken:void 0,scope:void 0,tokenType:void 0,username:void 0,exists:!1},I=a(4),N={notifications:[],loaders:{}},A=(y={},Object(I.a)(y,d.LOGIN_USER_GITHUB_OAUTH_SUCCESS,"Successfully logged in."),Object(I.a)(y,d.LOGIN_USER_GITHUB_OAUTH_FAILURE,"Failed to login through github. Please try again later"),Object(I.a)(y,d.UPDATE_USER_PROFILE_SUCCESS,"Successfully updated your profile."),Object(I.a)(y,d.UPDATE_USER_PROFILE_FAILURE,"Failed to update your profile. Please try again later"),Object(I.a)(y,d.FIND_GYM_REPOS,"We are updating your repositories."),Object(I.a)(y,d.FIND_GYM_REPOS_SUCCESS,"Successfully updated your repositories"),Object(I.a)(y,d.FIND_GYM_REPOS_FAILURE,"Failed to update your repositories. Please try again later"),Object(I.a)(y,d.LOGOUT_USER_SUCCESS,"Successfully logged out."),Object(I.a)(y,d.LOGOUT_USER_FAILURE,"Failed to log out. Please try again later."),Object(I.a)(y,d.CREATE_ENVIRONMENT_SUCCESS,"Successfully uploaded environment."),Object(I.a)(y,d.CREATE_ENVIRONMENT_FAILURE,"Failed to upload environment. Please try again later."),Object(I.a)(y,d.EDIT_ENVIRONMENT_SUCCESS,"Successfully updated environment."),Object(I.a)(y,d.EDIT_ENVIRONMENT_FAILURE,"Failed to update environment. Please try again later."),Object(I.a)(y,d.DELETE_ENVIRONMENT_SUCCESS,"Successfully deleted environment."),Object(I.a)(y,d.DELETE_ENVIRONMENT_FAILURE,"Failed to delete environment. Please try again later."),Object(I.a)(y,d.CREATE_IMAGE_SUCCESS,"Successfully uploaded image."),Object(I.a)(y,d.CREATE_IMAGE_FAILURE,"Failed to upload image. Please try again later."),Object(I.a)(y,d.DELETE_IMAGE_SUCCESS,"Successfully deleted image."),Object(I.a)(y,d.DELETE_IMAGE_FAILURE,"Failed to delete image. Please try again later."),Object(I.a)(y,d.CREATE_COMMENT_SUCCESS,"Successfully posted a reply."),Object(I.a)(y,d.CREATE_COMMENT_FAILURE,"Failed to post a reply. Please try again later."),Object(I.a)(y,d.EDIT_COMMENT_SUCCESS,"Successfully updated your reply."),Object(I.a)(y,d.EDIT_COMMENT_FAILURE,"Failed to update your reply. Please try again later."),Object(I.a)(y,d.DELETE_COMMENT_SUCCESS,"Successfully deleted your reply."),Object(I.a)(y,d.DELETE_COMMENT_FAILURE,"Failed to delete your reply. Please try again later."),Object(I.a)(y,d.CREATE_MESSAGEBOARD_SUCCESS,"Successfully opened a new discussion."),Object(I.a)(y,d.CREATE_MESSAGEBOARD_FAILURE,"Failed to open a new discussion. Please try again later."),Object(I.a)(y,d.EDIT_MESSAGEBOARD_SUCCESS,"Successfully updated your discussion."),Object(I.a)(y,d.EDIT_MESSAGEBOARD_FAILURE,"Failed to update your discussion. Please try again later."),Object(I.a)(y,d.DELETE_MESSAGEBOARD_SUCCESS,"Successfully deleted your message board."),Object(I.a)(y,d.DELETE_MESSAGEBOARD_FAILURE,"Failed to delete your message board. Please try again later."),y);function U(e,t){var a=A[t],n={key:T()(),message:a};return a?e.notifications.concat([n]):e.notifications}var j=function(e,t,a){return Object(v.a)({},e,{loaders:Object(v.a)({},e.loaders,Object(I.a)({},t,!0)),notifications:U(e,a)})},w=function(e,t,a){return{loaders:Object(v.a)({},e.loaders,Object(I.a)({},t,!1)),notifications:U(e,a)}};function M(e,t){return!0===e.loaders[t]}var L={environments:[],searchedEnvironments:void 0,categorizedEnvironments:void 0,searchedTopic:void 0,uploadSuccess:void 0,deleteSuccess:void 0},k={};function D(e){var t=e.response.data;return Object.keys(t).reduce(function(e,a){return Object(v.a)({},e,Object(I.a)({},a,t[a][0]))},{})}function G(e,t){var a=e[t];return a||!1}var x={userRepositories:[],repositories:[]},F={topics:[]},P={userImages:[],imageConfig:[],deleteSuccess:void 0,uploadedImage:void 0},B={contributors:[]},W={projectauthors:[]},V={messageboards:[],num_comments:{},uploadSuccess:void 0,deleteSuccess:void 0},H={comments:[],uploadSuccess:void 0,deleteSuccess:void 0},z=Object(c.c)({config:function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:C,t=arguments.length>1?arguments[1]:void 0;switch(t.type){case d.GET_API_CONFIG_SUCCESS:return Object(v.a)({},e,t.payload,{loaded:!0});default:return e}},display:function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:N,t=arguments.length>1?arguments[1]:void 0;switch(t.type){case d.LOGIN_USER_GITHUB_OAUTH:case d.REFRESH_AUTH_TOKEN:case d.GET_USER_PROFILE:return j(e,d.LOGIN_USER_GITHUB_OAUTH,t.type);case d.LOGIN_USER_GITHUB_OAUTH_SUCCESS:case d.LOGIN_USER_GITHUB_OAUTH_FAILURE:case d.REFRESH_AUTH_TOKEN_SUCCESS:case d.REFRESH_AUTH_TOKEN_FAILURE:case d.GET_USER_PROFILE_SUCCESS:case d.GET_USER_PROFILE_FAILURE:return w(e,d.LOGIN_USER_GITHUB_OAUTH,t.type);case d.LOGOUT_USER:case d.LOGOUT_USER_SUCCESS:case d.LOGOUT_USER_FAILURE:case d.UPDATE_USER_PROFILE:return j(e,d.UPDATE_USER_PROFILE,t.type);case d.UPDATE_USER_PROFILE_SUCCESS:case d.UPDATE_USER_PROFILE_FAILURE:return w(e,d.UPDATE_USER_PROFILE,t.type);case d.FIND_GYM_REPOS:return j(e,d.FIND_GYM_REPOS,t.type);case d.FIND_GYM_REPOS_SUCCESS:case d.FIND_GYM_REPOS_FAILURE:return w(e,d.FIND_GYM_REPOS,t.type);case d.CREATE_ENVIRONMENT:return j(e,d.CREATE_ENVIRONMENT,t.type);case d.CREATE_ENVIRONMENT_SUCCESS:case d.CREATE_ENVIRONMENT_FAILURE:return w(e,d.CREATE_ENVIRONMENT,t.type);case d.EDIT_ENVIRONMENT:return j(e,d.EDIT_ENVIRONMENT,t.type);case d.EDIT_ENVIRONMENT_SUCCESS:case d.EDIT_ENVIRONMENT_FAILURE:return w(e,d.EDIT_ENVIRONMENT,t.type);case d.DELETE_ENVIRONMENT:return j(e,d.DELETE_ENVIRONMENT,t.type);case d.DELETE_ENVIRONMENT_SUCCESS:case d.DELETE_ENVIRONMENT_FAILURE:return w(e,d.DELETE_ENVIRONMENT,t.type);case d.CREATE_IMAGE:return j(e,d.CREATE_IMAGE,t.type);case d.CREATE_IMAGE_SUCCESS:case d.CREATE_IMAGE_FAILURE:return w(e,d.CREATE_IMAGE,t.type);case d.DELETE_IMAGE:return j(e,d.DELETE_IMAGE,t.type);case d.DELETE_IMAGE_SUCCESS:case d.DELETE_IMAGE_FAILURE:return w(e,d.DELETE_IMAGE,t.type);case d.CREATE_MESSAGEBOARD:return j(e,d.CREATE_MESSAGEBOARD,t.type);case d.CREATE_MESSAGEBOARD_SUCCESS:case d.CREATE_MESSAGEBOARD_FAILURE:return w(e,d.CREATE_MESSAGEBOARD,t.type);case d.EDIT_MESSAGEBOARD:return j(e,d.EDIT_MESSAGEBOARD,t.type);case d.EDIT_MESSAGEBOARD_SUCCESS:case d.EDIT_MESSAGEBOARD_FAILURE:return w(e,d.EDIT_MESSAGEBOARD,t.type);case d.DELETE_MESSAGEBOARD:return j(e,d.DELETE_MESSAGEBOARD,t.type);case d.DELETE_MESSAGEBOARD_SUCCESS:case d.DELETE_MESSAGEBOARD_FAILURE:return w(e,d.DELETE_MESSAGEBOARD,t.type);case d.GET_COMMENTS_LIST:return j(e,d.GET_COMMENTS_LIST,t.type);case d.GET_COMMENTS_LIST_SUCCESS:case d.GET_COMMENTS_LIST_FAILURE:return w(e,d.GET_COMMENTS_LIST,t.type);case d.CREATE_COMMENT:return j(e,d.CREATE_COMMENT,t.type);case d.CREATE_COMMENT_SUCCESS:case d.CREATE_COMMENT_FAILURE:return w(e,d.CREATE_COMMENT,t.type);case d.EDIT_COMMENT:return j(e,d.EDIT_COMMENT,t.type);case d.EDIT_COMMENT_SUCCESS:case d.EDIT_COMMENT_FAILURE:return w(e,d.EDIT_COMMENT,t.type);case d.DELETE_COMMENT:return j(e,d.DELETE_COMMENT,t.type);case d.DELETE_COMMENT_SUCCESS:case d.DELETE_COMMENT_FAILURE:return w(e,d.DELETE_COMMENT,t.type);default:return e}},user:function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:R,t=arguments.length>1?arguments[1]:void 0;switch(t.type){case d.LOGIN_USER_GITHUB_OAUTH_SUCCESS:case d.REFRESH_AUTH_TOKEN_SUCCESS:case d.GET_USER_PROFILE_SUCCESS:return Object(v.a)({},e,t.payload,{exists:!0});case d.UPDATE_USER_PROFILE_SUCCESS:return Object(v.a)({},e,t.payload);case d.DELETE_USER_SUCCESS:case d.LOGOUT_USER_SUCCESS:return Object(v.a)({},R,{exists:!1});default:return e}},environments:function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:L,t=arguments.length>1?arguments[1]:void 0;switch(t.type){case d.GET_ENVIRONMENTS_LIST_SUCCESS:return Object(v.a)({},e,{environments:t.payload.results});case d.DELETE_ENVIRONMENT_SUCCESS:return Object(v.a)({},e,{deleteSuccess:!0});case d.DELETE_ENVIRONMENT_FAILURE:return Object(v.a)({},e,{deleteSuccess:!1});case d.CREATE_ENVIRONMENT_SUCCESS:return Object(v.a)({},e,{uploadSuccess:!0});case d.CREATE_ENVIRONMENT_FAILURE:return Object(v.a)({},e,{uploadSuccess:!1});case d.EDIT_ENVIRONMENT_SUCCESS:return Object(v.a)({},e,{uploadSuccess:!0});case d.EDIT_ENVIRONMENT_FAILURE:return Object(v.a)({},e,{uploadSuccess:!1});case d.RESET_ENVIRONMENTS_PROPS:return Object(v.a)({},e,{uploadSuccess:void 0,deleteSuccess:void 0});case d.SEARCH_ENVIRONMENTS_SUCCESS:return Object(v.a)({},e,{searchedEnvironments:t.payload});case d.SEARCH_ENVIRONMENTS_FAILURE:return Object(v.a)({},e,{searchedEnvironments:[]});case d.SEARCH_ENVIRONMENTS_RESET:return Object(v.a)({},e,{searchedEnvironments:void 0});case d.CATEGORIZE_ENVIRONMENTS_SUCCESS:return Object(v.a)({},e,{categorizedEnvironments:t.environment,searchedTopic:t.topic});case d.CATEGORIZE_ENVIRONMENTS_FAILURE:return Object(v.a)({},e,{categorizedEnvironments:[]});case d.CATEGORIZE_ENVIRONMENTS_RESET:return Object(v.a)({},e,{categorizedEnvironments:void 0,searchedTopic:void 0});default:return e}},errors:function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:k,t=arguments.length>1?arguments[1]:void 0;switch(t.type){case d.UPDATE_USER_PROFILE:case d.UPDATE_USER_PROFILE_SUCCESS:return Object(v.a)({},e,Object(I.a)({},d.UPDATE_USER_PROFILE,null));case d.UPDATE_USER_PROFILE_FAILURE:return Object(v.a)({},e,Object(I.a)({},d.UPDATE_USER_PROFILE,D(t.payload)));case d.CREATE_ENVIRONMENT:case d.CREATE_ENVIRONMENT_SUCCESS:return Object(v.a)({},e,Object(I.a)({},d.CREATE_ENVIRONMENT,null));case d.CREATE_ENVIRONMENT_FAILURE:return Object(v.a)({},e,Object(I.a)({},d.CREATE_ENVIRONMENT,D(t.payload)));case d.EDIT_ENVIRONMENT:case d.EDIT_ENVIRONMENT_SUCCESS:return Object(v.a)({},e,Object(I.a)({},d.EDIT_ENVIRONMENT,null));case d.EDIT_ENVIRONMENT_FAILURE:return Object(v.a)({},e,Object(I.a)({},d.EDIT_ENVIRONMENT,D(t.payload)));case d.RESET_ENVIRONMENTS_ERRORS:var a;return Object(v.a)({},e,(a={},Object(I.a)(a,d.EDIT_ENVIRONMENT,null),Object(I.a)(a,d.CREATE_ENVIRONMENT,null),a));case d.CREATE_MESSAGEBOARD:case d.CREATE_MESSAGEBOARD_SUCCESS:return Object(v.a)({},e,Object(I.a)({},d.CREATE_MESSAGEBOARD,null));case d.CREATE_MESSAGEBOARD_FAILURE:return Object(v.a)({},e,Object(I.a)({},d.CREATE_MESSAGEBOARD,D(t.payload)));case d.EDIT_MESSAGEBOARD:case d.EDIT_MESSAGEBOARD_SUCCESS:return Object(v.a)({},e,Object(I.a)({},d.EDIT_MESSAGEBOARD,null));case d.EDIT_MESSAGEBOARD_FAILURE:return Object(v.a)({},e,Object(I.a)({},d.EDIT_MESSAGEBOARD,D(t.payload)));case d.RESET_MESSAGEBOARDS_ERRORS:var n;return Object(v.a)({},e,(n={},Object(I.a)(n,d.EDIT_MESSAGEBOARD,null),Object(I.a)(n,d.CREATE_MESSAGEBOARD,null),n));case d.CREATE_COMMENT:case d.CREATE_COMMENT_SUCCESS:return Object(v.a)({},e,Object(I.a)({},d.CREATE_COMMENT,null));case d.CREATE_COMMENT_FAILURE:return Object(v.a)({},e,Object(I.a)({},d.CREATE_COMMENT,D(t.payload)));case d.EDIT_COMMENT:case d.EDIT_COMMENT_SUCCESS:return Object(v.a)({},e,Object(I.a)({},d.EDIT_COMMENT,null));case d.EDIT_COMMENT_FAILURE:return Object(v.a)({},e,Object(I.a)({},d.EDIT_COMMENT,D(t.payload)));case d.RESET_COMMENTS_ERRORS:var r;return Object(v.a)({},e,(r={},Object(I.a)(r,d.EDIT_COMMENT,null),Object(I.a)(r,d.CREATE_COMMENT,null),r));default:return e}},repositories:function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:x,t=arguments.length>1?arguments[1]:void 0;switch(t.type){case d.GET_USER_REPOSITORIES_LIST_SUCCESS:return Object(v.a)({},e,{userRepositories:t.payload});case d.GET_REPOSITORIES_SUCCESS:return Object(v.a)({},e,{repositories:t.payload.results});default:return e}},contributors:function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:B,t=arguments.length>1?arguments[1]:void 0;switch(t.type){case d.GET_CONTRIBUTORS_LIST_SUCCESS:return Object(v.a)({},e,{contributors:t.payload.results});default:return e}},projectauthors:function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:W,t=arguments.length>1?arguments[1]:void 0;switch(t.type){case d.GET_PROJECTAUTHORS_LIST_SUCCESS:return Object(v.a)({},e,{projectauthors:t.payload.results});default:return e}},topics:function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:F,t=arguments.length>1?arguments[1]:void 0;switch(t.type){case d.GET_TOPICS_SUCCESS:return Object(v.a)({},e,{topics:t.payload.results});default:return e}},images:function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:P,t=arguments.length>1?arguments[1]:void 0;switch(t.type){case d.CREATE_IMAGE_SUCCESS:return Object(v.a)({},e,{uploadedImage:t.payload});case d.CREATE_IMAGE_FAILURE:return Object(v.a)({},e,{uploadedImage:!1});case d.DELETE_IMAGE_SUCCESS:return Object(v.a)({},e,{deleteSuccess:!0});case d.DELETE_IMAGE_FAILURE:return Object(v.a)({},e,{deleteSuccess:!1});case d.GET_USER_IMAGES_LIST_SUCCESS:return Object(v.a)({},e,{userImages:t.payload});case d.GET_IMAGE_CONFIG_LIST_SUCCESS:return Object(v.a)({},e,{imageConfig:t.payload});case d.RESET_IMAGE_PROPS:return Object(v.a)({},e,{deleteSuccess:void 0,uploadedImage:void 0});default:return e}},messageboards:function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:V,t=arguments.length>1?arguments[1]:void 0;switch(t.type){case d.GET_MESSAGEBOARDS_LIST_SUCCESS:return Object(v.a)({},e,{messageboards:t.payload.results});case d.DELETE_MESSAGEBOARD_SUCCESS:return Object(v.a)({},e,{deleteSuccess:!0});case d.DELETE_MESSAGEBOARD_FAILURE:return Object(v.a)({},e,{deleteSuccess:!1});case d.CREATE_MESSAGEBOARD_SUCCESS:return Object(v.a)({},e,{uploadSuccess:!0});case d.CREATE_MESSAGEBOARD_FAILURE:return Object(v.a)({},e,{uploadSuccess:!1});case d.EDIT_MESSAGEBOARD_SUCCESS:return Object(v.a)({},e,{uploadSuccess:!0});case d.EDIT_MESSAGEBOARD_FAILURE:return Object(v.a)({},e,{uploadSuccess:!1});case d.RESET_MESSAGEBOARDS_PROPS:return Object(v.a)({},e,{uploadSuccess:void 0,deleteSuccess:void 0});case d.COUNT_COMMENTS_SUCCESS:return Object(v.a)({},e,{num_comments:t.payload});default:return e}},comments:function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:H,t=arguments.length>1?arguments[1]:void 0;switch(t.type){case d.GET_COMMENTS_LIST_SUCCESS:return Object(v.a)({},e,{comments:t.payload});case d.DELETE_COMMENT_SUCCESS:return Object(v.a)({},e,{deleteSuccess:!0});case d.DELETE_COMMENT_FAILURE:return Object(v.a)({},e,{deleteSuccess:!1});case d.CREATE_COMMENT_SUCCESS:return Object(v.a)({},e,{uploadSuccess:!0});case d.CREATE_COMMENT_FAILURE:return Object(v.a)({},e,{uploadSuccess:!1});case d.EDIT_COMMENT_SUCCESS:return Object(v.a)({},e,{uploadSuccess:!0});case d.EDIT_COMMENT_FAILURE:return Object(v.a)({},e,{uploadSuccess:!1});case d.RESET_COMMENTS_PROPS:return Object(v.a)({},e,{uploadSuccess:void 0,deleteSuccess:void 0});case d.COUNT_COMMENTS_SUCCESS:return Object(v.a)({},e,{num_comments:t.payload});case d.RESET_COMMENTS:return Object(v.a)({},e,{comments:[]});default:return e}}}),Y=a(7),q=a(8),K=a(11),Z=a(10),J=a(12),X=a(468),Q=a(472),$=a(471),ee=a(37),te=a.n(ee),ae=a(469),ne=new(function(){function e(t,a){Object(Y.a)(this,e),this.base=t,this.version=a,this.url="".concat(t,"/").concat(a)}return Object(q.a)(e,[{key:"config",value:function(){return m.a.get("".concat(this.url,"/app_config/"))}},{key:"login",value:function(e){return m.a.post("".concat(this.url,"/users/").concat(e,"/github_oauth/"))}},{key:"logout",value:function(e){return m.a.post("".concat(this.url,"/users/logout/"))}},{key:"refreshToken",value:function(e){return m.a.post("".concat(this.url,"/users/").concat(e,"/refresh_token/"))}},{key:"me",value:function(){return m.a.get("".concat(this.url,"/users/me/"))}},{key:"updateMe",value:function(e){return m.a.post("".concat(this.url,"/users/update_me/"),e)}},{key:"deleteMe",value:function(){return m.a.post("".concat(this.url,"/users/delete_me/"))}},{key:"status",value:function(){return m.a.get("".concat(this.base,"/watchman/"))}},{key:"environments",value:function(e){return m.a.get("".concat(this.url,"/environments/"))}},{key:"createEnvironment",value:function(e,t,a,n,r,o){return m.a.post("".concat(this.url,"/environments/"),{name:e,description:t,repository:a,tags:n,topic:r,avatar:o})}},{key:"editEnvironment",value:function(e){return m.a.put("".concat(this.url,"/environments/").concat(e.id,"/"),e)}},{key:"deleteEnvironment",value:function(e){return m.a.delete("".concat(this.url,"/environments/").concat(e.id,"/"))}},{key:"searchEnvironments",value:function(e){return m.a.get("".concat(this.url,"/environments/filter/?search=").concat(e))}},{key:"topics",value:function(){return m.a.get("".concat(this.url,"/topics/"))}},{key:"searchEnvironmentsByTopic",value:function(e){return m.a.get("".concat(this.url,"/environments/filter_topic/?topic=").concat(e))}},{key:"createImage",value:function(e){var t=new FormData;return t.append("file",e),m.a.post("".concat(this.url,"/images/"),t,{headers:{"Content-Type":"multipart/form-data"}})}},{key:"myImages",value:function(){return m.a.get("".concat(this.url,"/images/mine/"))}},{key:"deleteImage",value:function(e){return m.a.delete("".concat(this.url,"/images/").concat(e.id,"/"))}},{key:"imageConfig",value:function(){return m.a.get("".concat(this.url,"/image_config/"))}},{key:"repositories",value:function(){return m.a.get("".concat(this.url,"/repositories/"))}},{key:"myRepositories",value:function(){return m.a.get("".concat(this.url,"/repositories/mine/"))}},{key:"findGymRepos",value:function(){return m.a.post("".concat(this.url,"/repositories/find_gym_repos/"))}},{key:"getContributors",value:function(){return m.a.get("".concat(this.url,"/contributors/"))}},{key:"getProjectAuthors",value:function(){return m.a.get("".concat(this.url,"/projectauthors/"))}},{key:"getMessageBoards",value:function(e){return m.a.get("".concat(this.url,"/message_boards/"))}},{key:"createMessageBoard",value:function(e){return m.a.post("".concat(this.url,"/message_boards/"),e)}},{key:"editMessageBoard",value:function(e){return m.a.put("".concat(this.url,"/message_boards/").concat(e.id,"/"),e)}},{key:"deleteMessageBoard",value:function(e){return m.a.delete("".concat(this.url,"/message_boards/").concat(e.id,"/"))}},{key:"countComments",value:function(){return m.a.get("".concat(this.url,"/comments/count_comments/"))}},{key:"getComments",value:function(e){return m.a.get("".concat(this.url,"/comments/board_comments/"),{params:{messageboard:e}})}},{key:"createComment",value:function(e,t){return m.a.post("".concat(this.url,"/comments/"),{comment:e,messageboard:t})}},{key:"editComment",value:function(e,t){return m.a.put("".concat(this.url,"/comments/").concat(e,"/"),{comment:t})}},{key:"deleteComment",value:function(e){return m.a.delete("".concat(this.url,"/comments/").concat(e.id,"/"))}}]),e}())("".concat(f()?"http://localhost:8000":g,"/api"),"v1");function re(e){console.error(e)}var oe=function(e){function t(){return Object(Y.a)(this,t),Object(K.a)(this,Object(Z.a)(t).apply(this,arguments))}return Object(J.a)(t,e),Object(q.a)(t,[{key:"componentWillMount",value:function(){var e=this.getUrlParams("code"),t=this.getUrlParams("state");this.props.loginUserWithGithub(e,t)}},{key:"getUrlParams",value:function(e){var t={};return decodeURIComponent(window.location.href.slice(window.location.href.indexOf("?")+1)).split("&").forEach(function(e,a){var n=e.split("=",2);t[n[0]]=n[1]}),e&&e in t?t[e]:t}},{key:"render",value:function(){var e=this.props.callbackURL,t=e?"/env/".concat(e):"/";return r.a.createElement(ae.a,{to:t})}}]),t}(n.Component);var ie=Object(s.b)(function(e,t){return{callbackURL:t.match.params.callbackURL}},{loginUserWithGithub:function(e,t){return function(a){a({type:d.LOGIN_USER_GITHUB_OAUTH,payload:t}),ne.login(e).then(function(e){a({type:d.LOGIN_USER_GITHUB_OAUTH_SUCCESS,payload:e.data})}).catch(function(e){a({type:d.LOGIN_USER_GITHUB_OAUTH_FAILURE}),re(e)})}}})(oe),se=function(e){e({type:d.GET_USER_REPOSITORIES_LIST}),ne.myRepositories().then(function(t){e({type:d.GET_USER_REPOSITORIES_LIST_SUCCESS,payload:t.data})}).catch(function(t){e({type:d.GET_USER_REPOSITORIES_LIST_FAILURE}),re(t)})},ce=function(){return se},le=function(e){e({type:d.GET_USER_IMAGES_LIST}),ne.myImages().then(function(t){e({type:d.GET_USER_IMAGES_LIST_SUCCESS,payload:t.data})}).catch(function(t){e({type:d.GET_USER_IMAGES_LIST_FAILURE}),re(t)})},ue=function(){return le},Ee=function(e){e({type:d.GET_IMAGE_CONFIG_LIST}),ne.imageConfig().then(function(t){e({type:d.GET_IMAGE_CONFIG_LIST_SUCCESS,payload:t.data})}).catch(function(t){e({type:d.GET_IMAGE_CONFIG_LIST_FAILURE}),re(t)})},me=function(e){function t(){return Object(Y.a)(this,t),Object(K.a)(this,Object(Z.a)(t).apply(this,arguments))}return Object(J.a)(t,e),Object(q.a)(t,[{key:"componentDidUpdate",value:function(e,t){!e.userExists&&this.props.userExists&&(this.props.getUserRepositories(),this.props.getUserImages())}},{key:"render",value:function(){return r.a.createElement(r.a.Fragment,null)}}]),t}(n.Component),de={getUserRepositories:ce,getUserImages:ue},pe=Object(s.b)(function(e){return{userExists:e.user.exists}},de)(me),he=a(467),ge=a(94),Se=a.n(ge),fe=a(43),be=a.n(fe),ye=a(149),Oe=a.n(ye),ve=a(121),_e=a.n(ve),Te=a(32),Ce=a(31),Re=a.n(Ce),Ie=function(){return r.a.createElement("img",{src:b.STATIC_URL.concat(b.SCIGYM_LOGO),height:"150",width:"150",alt:""})},Ne=function(){return r.a.createElement("img",{src:b.STATIC_URL.concat(b.GITHUB_LOGO),height:"48",width:"48",alt:""})},Ae=function(){return r.a.createElement("img",{src:b.STATIC_URL.concat(b.SCIGYM_LOGO),height:"37",width:"37",alt:"",style:{padding:"5px"}})},Ue=function(){return r.a.createElement("img",{src:b.STATIC_URL.concat(b.TWITTER_LOGO),height:"48",width:"48",alt:""})},je=a(47),we=a(21),Me=a.n(we),Le=a(78),ke=a.n(Le),De=a(77),Ge=a.n(De),xe=a(39),Fe=a.n(xe),Pe=a(93),Be=a.n(Pe),We=a(56),Ve=a.n(We),He=a(46),ze=a.n(He),Ye=a(197),qe=a.n(Ye),Ke=a(41),Ze=a.n(Ke),Je=a(40),Xe=a.n(Je),Qe=a(51),$e=a.n(Qe),et=a(91),tt=a.n(et),at=a(92),nt=a.n(at),rt=a(2),ot=a.n(rt),it=a(119),st=a.n(it),ct=function(e){function t(e){var a;return Object(Y.a)(this,t),(a=Object(K.a)(this,Object(Z.a)(t).call(this,e))).handleCheck=function(e){a.setState({checked:e.target.checked})},a.state={checked:!1},a}return Object(J.a)(t,e),Object(q.a)(t,[{key:"render",value:function(){var e=this.props,t=e.open,a=e.onClose,n=e.classes,o=this.state.checked;return r.a.createElement(Xe.a,{open:t,onClose:a},r.a.createElement(Ze.a,{id:"login-title"},"Login to SciGym"),r.a.createElement(tt.a,null,r.a.createElement(nt.a,{id:"login-description"},"Login via Github to upload and discuss environments on SciGym"),r.a.createElement(ot.a,{className:n.textField},r.a.createElement(st.a,{checked:o,onChange:this.handleCheck,value:"policy"}),"I agree to the ",r.a.createElement(he.a,{component:"button",to:"/policy/private-policy",target:"_blank",rel:"noopener"},"Private Policy")," and ",r.a.createElement(he.a,{component:"button",to:"/policy/terms-and-conditions",target:"_blank",rel:"noopener"},"Terms and Conditions"))),r.a.createElement($e.a,null,o?r.a.createElement(Me.a,{fullWidth:!0,onClick:a,variant:"contained",color:"primary",size:"large",autoFocus:!0,href:this.githubOauthLink},"Login with Github"):r.a.createElement(Me.a,{fullWidth:!0,disabled:!0,variant:"contained",size:"large",autoFocus:!0},"Login with Github")))}},{key:"githubOauthLink",get:function(){var e=this.props,t=e.githubClientId,a=e.githubRandomState,n=e.callbackURL,r=this.props.githubCallbackUrl;return n.length>0&&(r=r.concat(n)),"https://github.com/login/oauth/authorize?client_id=".concat(t,"&redirect_uri=").concat(r,"&state=").concat(a)}}]),t}(n.Component),lt=Object(c.d)(Object(s.b)(function(e){return{githubClientId:e.config.githubClientId,githubCallbackUrl:e.config.githubCallbackUrl,githubRandomState:e.config.githubRandomState}}),Object(Te.withStyles)(function(e){return{}}))(ct),ut=a(13),Et=function(e){function t(e){var a;return Object(Y.a)(this,t),(a=Object(K.a)(this,Object(Z.a)(t).call(this,e))).logoutUser=a.logoutUser.bind(Object(ut.a)(Object(ut.a)(a))),a}return Object(J.a)(t,e),Object(q.a)(t,[{key:"logoutUser",value:function(){this.props.onClick(),this.props.logout(this.props.appClientId)}},{key:"render",value:function(){return r.a.createElement(ze.a,{onClick:this.logoutUser,component:he.a,to:"/"},"Logout")}}]),t}(n.Component),mt={logout:function(e){return function(t){t({type:d.LOGOUT_USER,payload:null}),ne.logout(e).then(function(e){t({type:d.LOGOUT_USER_SUCCESS,payload:e.data})}).catch(function(e){t({type:d.LOGOUT_USER_FAILURE,payload:null}),re(e)})}}},dt=Object(s.b)(function(e){return{appClientId:e.config.appClientId}},mt)(Et);var pt=Object(c.d)(Object(s.b)(function(e){return{userExists:e.user.exists}}),Object(Te.withStyles)(function(e){return{root:{display:"flex"},paper:{marginRight:2*e.spacing.unit}}}))(function(e){var t=e.classes,a=e.userExists,o=Object(n.useState)(!1),i=Object(je.a)(o,2),s=i[0],c=i[1],l=Object(n.useState)(void 0),u=Object(je.a)(l,2),E=u[0],m=u[1],d=Object(n.useState)(!1),p=Object(je.a)(d,2),h=p[0],g=p[1],S=function(){return c(!1)},f=function(){c(!1),g(!0)};return r.a.createElement("div",{className:t.root},r.a.createElement("div",null,r.a.createElement(Me.a,{buttonRef:function(e){return m(e)},"aria-owns":s?"menu-list-grow":void 0,"aria-haspopup":"true",onClick:function(){return c(!s)}},r.a.createElement(qe.a,null)),r.a.createElement(Be.a,{open:s,anchorEl:E,transition:!0,disablePortal:!0},function(e){var t=e.TransitionProps,n=e.placement;return r.a.createElement(Ge.a,Object.assign({},t,{id:"menu-list-grow",style:{transformOrigin:"bottom"===n?"center top":"center bottom"}}),r.a.createElement(Fe.a,null,r.a.createElement(ke.a,{onClickAway:S},r.a.createElement(Ve.a,null,a?r.a.createElement(ze.a,{onClick:S,component:he.a,to:"/profile"},"My Profile"):r.a.createElement(ze.a,{onClick:f},"Login"),a&&r.a.createElement(dt,{onClick:S}),r.a.createElement(ze.a,{onClick:S,component:he.a,to:"/impressum"},"About Us")))))})),r.a.createElement(lt,{open:h,onClose:function(){return g(!1)},callbackURL:""}))}),ht=a(198),gt=a.n(ht),St=a(76),ft=a.n(St),bt=a(59);function yt(e,t,a){var n;return function(){var r=this,o=arguments,i=a&&!n;clearTimeout(n),n=setTimeout(function(){n=null,a||e.apply(r,o)},t),i&&e.apply(r,o)}}var Ot=function(e){return function(t){t({type:d.GET_MESSAGEBOARDS_LIST}),ne.getMessageBoards(e).then(function(e){t({type:d.GET_MESSAGEBOARDS_LIST_SUCCESS,payload:e.data})}).catch(function(e){t({type:d.GET_MESSAGEBOARDS_LIST_FAILURE}),re(e)})}},vt=function(){for(var e=arguments.length,t=new Array(e),a=0;a0&&t.map(function(e){return r.a.createElement(ze.a,{key:e.id,value:e.name,component:he.a,to:"/env/"+e.name,onClick:a},e.name)}))},Gt=10,xt=function(e){function t(e){var a;return Object(Y.a)(this,t),(a=Object(K.a)(this,Object(Z.a)(t).call(this,e))).handleExpandMore=function(){a.setState({visibleEnvironmentCount:a.state.visibleEnvironmentCount+Gt})},a.handleExpandLess=function(){a.setState({visibleEnvironmentCount:a.state.visibleEnvironmentCount-Gt})},a.handleClose=function(){a.setState({open:!1})},a.state={searchValue:"",open:!1,visibleEnvironmentCount:Gt},a.handleSearch=a.handleSearch.bind(Object(ut.a)(Object(ut.a)(a))),a.delayedSearch=yt(a.props.searchEnvironments,500),a.delayedReset=yt(a.props.resetSearchedEnvironments,500),a}return Object(J.a)(t,e),Object(q.a)(t,[{key:"handleSearch",value:function(e){e.preventDefault(),this.setState({searchValue:e.target.value,open:!0}),e.target.value.trim().length>=3?this.delayedSearch(e.target.value):this.delayedReset()}},{key:"render",value:function(){var e=this,t=this.state,a=t.searchValue,n=t.open,o=this.props.classes,i=this.props.searchedEnvironments?this.props.searchedEnvironments:this.props.environments,s=this.state.visibleEnvironmentCount>=i.length,c=this.state.visibleEnvironmentCount<=Gt,l=i.slice(0,this.state.visibleEnvironmentCount);return r.a.createElement("div",{className:o.search},r.a.createElement(ke.a,{onClickAway:this.handleClose},r.a.createElement("div",null,r.a.createElement("div",{className:o.searchIcon},r.a.createElement(gt.a,null)),r.a.createElement(ft.a,{placeholder:"Search environments\u2026",onChange:this.handleSearch,onClick:this.handleSearch,value:a,classes:{root:o.inputRoot,input:o.inputInput}}),r.a.createElement(Be.a,{open:n,transition:!0,disablePortal:!0},function(t){var a=t.TransitionProps,n=t.placement;return r.a.createElement(Ge.a,Object.assign({},a,{id:"menu-list-grow",style:{transformOrigin:"bottom"===n?"center top":"center bottom"}}),r.a.createElement(Fe.a,{className:o.menuStyle,elevation:20},r.a.createElement(Dt,{environments:l,handleClose:e.handleClose}),r.a.createElement(jt,{classes:o,allEnvVisible:s,noEnvVisible:c,handleExpandMore:e.handleExpandMore,handleExpandLess:e.handleExpandLess})))}))))}}]),t}(n.Component),Ft={searchEnvironments:function(e){var t=e.replace(/[ ,]+/g,",");return function(a){a({type:d.SEARCH_ENVIRONMENTS,payload:e}),ne.searchEnvironments(t).then(function(e){a({type:d.SEARCH_ENVIRONMENTS_SUCCESS,payload:e.data})}).catch(function(e){a({type:d.SEARCH_ENVIRONMENTS_FAILURE}),re(e)})}},resetSearchedEnvironments:function(){return function(e){e({type:d.SEARCH_ENVIRONMENTS_RESET})}}},Pt=Object(c.d)(Object(s.b)(function(e){return{environments:e.environments.environments,searchedEnvironments:e.environments.searchedEnvironments}},Ft),Object(Te.withStyles)(function(e){var t;return{search:Object(I.a)({position:"relative",borderRadius:e.shape.borderRadius,backgroundColor:Object(bt.fade)(e.palette.common.white,.15),"&:hover":{backgroundColor:Object(bt.fade)(e.palette.common.white,.25)},marginLeft:6*e.spacing.unit,width:"auto"},e.breakpoints.down("xs"),{backgroundColor:Object(bt.fade)(e.palette.primary.main,.15),"&:hover":{backgroundColor:Object(bt.fade)(e.palette.primary.main,.25)},margin:2*e.spacing.unit}),searchIcon:Object(I.a)({width:9*e.spacing.unit,height:"100%",position:"absolute",pointerEvents:"none",display:"flex",alignItems:"center",justifyContent:"center"},e.breakpoints.down("xs"),{width:4*e.spacing.unit}),inputRoot:{color:"inherit",width:"100%"},inputInput:(t={paddingTop:e.spacing.unit,paddingRight:e.spacing.unit,paddingBottom:e.spacing.unit,paddingLeft:9*e.spacing.unit,transition:e.transitions.create("width"),width:"100%"},Object(I.a)(t,e.breakpoints.up("md"),{width:200}),Object(I.a)(t,e.breakpoints.down("xs"),{paddingLeft:4*e.spacing.unit}),t),menuStyle:{zIndex:1,minWidth:"200px",maxHeight:"200px",overflow:"auto"}}}))(xt),Bt=function(e){function t(){return Object(Y.a)(this,t),Object(K.a)(this,Object(Z.a)(t).apply(this,arguments))}return Object(J.a)(t,e),Object(q.a)(t,[{key:"render",value:function(){var e=this.props.loading,t=this.props.classes;return r.a.createElement(Se.a,{position:"sticky",color:"inherit"},r.a.createElement(_e.a,{className:t.toolBarStyle},r.a.createElement("div",{className:t.root},r.a.createElement(be.a,{component:he.a,to:"/",color:"inherit",className:t.iconButtonStyle},r.a.createElement(Ae,null),"SciGym"),r.a.createElement(Re.a,{xsDown:!0},r.a.createElement(be.a,{component:he.a,to:"/get-started",color:"inherit",className:t.iconButtonStyle},r.a.createElement(Oe.a,{className:t.leftIcon}),"Get Started")),r.a.createElement(Re.a,{smUp:!0},r.a.createElement(be.a,{component:he.a,to:"/get-started",color:"inherit",className:t.iconButtonStyle},r.a.createElement(Oe.a,null)))),r.a.createElement(Re.a,{xsDown:!0},r.a.createElement(Pt,null)),r.a.createElement("div",{className:t.grow}),r.a.createElement("div",{className:t.menuButton},e?r.a.createElement(te.a,{size:30,disableShrink:!0,color:"secondary"}):r.a.createElement(pt,null))))}}]),t}(n.PureComponent),Wt=Object($.a)(Object(c.d)(Object(s.b)(function(e){return{loading:M(e.display,d.LOGIN_USER_GITHUB_OAUTH)}}),Object(Te.withStyles)(function(e){return{root:{flex:1,flexDirection:"row",display:"flex"},grow:{flexGrow:1,display:"flex"},leftIcon:{marginRight:e.spacing.unit},menuButton:{position:"relative",marginLeft:-12,marginRight:20},toolBarStyle:{backgroundColor:"#82B1FF"},iconButtonStyle:{borderRadius:"0",backgroundColor:"transparent",textDecoration:"none",paddingBottom:"0",paddingTop:"0"}}}))(Bt)),Vt=a(34),Ht=a.n(Vt),zt=a(20),Yt=a.n(zt),qt=a(33),Kt=a.n(qt),Zt=a(35),Jt=a.n(Zt),Xt=a(71),Qt=a.n(Xt),$t=a(70),ea=a.n($t),ta=a(69),aa=a.n(ta),na=a(42),ra=a.n(na),oa=a(200),ia=a.n(oa),sa=a(150),ca=a.n(sa),la=function(e){var t=e.classes,a=e.scigym,n=e.gym;return r.a.createElement("div",{className:t.chipPosition},a?r.a.createElement("div",null,r.a.createElement(ra.a,{icon:r.a.createElement(ca.a,null),label:"SciGym Native",className:t.tagStyle,color:"primary"})):n?r.a.createElement("div",null,r.a.createElement(ra.a,{icon:r.a.createElement(ca.a,null),label:"Gym Verified",className:t.tagStyle,color:"primary",variant:"outlined"})):r.a.createElement("div",null,r.a.createElement(ra.a,{icon:r.a.createElement(ia.a,null),label:"Gym Unverified",className:t.tagStyle,color:"secondary",variant:"outlined"})))},ua=a(57),Ea=a.n(ua),ma=a(98),da=a.n(ma),pa=function(e){var t=e.classes,a=e.name,n=e.description,o=e.fork,i=e.owner,s=e.topic,c=e.tags;return r.a.createElement(Ea.a,null,r.a.createElement(ot.a,{variant:"h5",component:"h2",gutterBottom:!0},a),r.a.createElement(ot.a,{variant:"subtitle1",gutterBottom:!0},n),r.a.createElement(ot.a,{variant:"subtitle1",gutterBottom:!0},"Owner: ",i.username," ",o?r.a.createElement("b",null," (forked)"):""),r.a.createElement(ot.a,{variant:"subtitle1",gutterBottom:!0},"Category: ",s?r.a.createElement("b",null,s.name):r.a.createElement("b",null," None ")),r.a.createElement(Ht.a,null,c.map(function(e){return r.a.createElement(ra.a,{icon:r.a.createElement(da.a,null),label:e,key:e,clickable:!0,className:t.tagStyle,color:"primary",variant:"outlined"})})))},ha=function(e){function t(e){var a;return Object(Y.a)(this,t),(a=Object(K.a)(this,Object(Z.a)(t).call(this,e))).handleClickOpen=function(){var e=!a.state.open;a.setState({open:e})},a.handleClose=function(){a.setState({open:!1})},a.state={open:!1},a}return Object(J.a)(t,e),Object(q.a)(t,[{key:"render",value:function(){var e=this.props.environment.repository,t=e.owner,a=e.htmlUrl,n=e.gym,o=e.fork,i=this.props.environment,s=i.name,c=i.url,l=i.description,u=i.scigym,E=i.tags,m=i.topic,d=i.currentAvatar,p=this.props.classes,h=b.STATIC_URL+b.SCIGYM_LOGO;return null!=d&&(h=b.MEDIA_URL.concat(d.filePath)),r.a.createElement(Kt.a,{className:p.listStyle},r.a.createElement(aa.a,{direction:"up",in:!0,mountOnEnter:!0,unmountOnExit:!0},r.a.createElement(Jt.a,{className:p.cardStyle,raised:!0},r.a.createElement("div",{className:p.root},r.a.createElement("div",{className:p.logoStyle},r.a.createElement("img",{src:h,height:"150",width:"150",alt:""})),r.a.createElement("div",{className:p.cardContentStyle},r.a.createElement(ea.a,{component:he.a,to:"/env/"+c},r.a.createElement(pa,{classes:p,name:s,description:l,fork:o,owner:t,topic:m,tags:E})),r.a.createElement(Qt.a,null,r.a.createElement(Me.a,{href:a,className:p.buttonStyle,target:"_blank",rel:"noopener noreferrer"},r.a.createElement(Ne,null),"Github"),r.a.createElement(la,{classes:p,scigym:u,gym:n})))))))}}]),t}(n.Component),ga=Object(Te.withStyles)(function(e){var t;return{root:{display:"flex",flexFlow:"row wrap"},cardStyle:{width:"100%"},logoStyle:{flex:"1 1 10px",margin:"auto",top:"0",bottom:"0",textAlign:"center"},cardContentStyle:{flex:"1 1 400px"},buttonStyle:Object(I.a)({margin:e.spacing.unit},e.breakpoints.down("xs"),{margin:"0"}),tagStyle:Object(I.a)({margin:e.spacing.unit},e.breakpoints.down("xs"),{margin:"0"}),listStyle:(t={minWidth:"770px"},Object(I.a)(t,e.breakpoints.down("xs"),{minWidth:"0px"}),Object(I.a)(t,e.breakpoints.up("md"),{minWidth:"950px"}),t)}})(ha),Sa=a(201),fa=a.n(Sa),ba=a(79),ya=a.n(ba),Oa=function(e){var t=e.classes;return r.a.createElement(Fe.a,{className:t.overlay},r.a.createElement(ot.a,{variant:"h4",className:t.titleStyle},"Reinforcement Learning for Science"),r.a.createElement(Re.a,{smUp:!0},r.a.createElement(ot.a,{variant:"subtitle2",className:t.textStyle},"Welcome to ",r.a.createElement("b",null,"SciGym"),", the open source library for reinforcement learning environments in science.")),r.a.createElement(Re.a,{xsDown:!0},r.a.createElement(ot.a,{variant:"subtitle1",className:t.textStyle},"Welcome to ",r.a.createElement("b",null,"SciGym"),", the open source library for reinforcement learning environments in science.")),r.a.createElement(Re.a,{xsDown:!0},r.a.createElement("div",{className:t.mediaButtons},r.a.createElement(Me.a,{href:"https://github.com/hendrikpn/scigym",target:"_blank",rel:"noopener noreferrer"},r.a.createElement(Ne,null)),r.a.createElement(Me.a,{href:"https://twitter.com/scigym_ai",target:"_blank",rel:"noopener noreferrer"},r.a.createElement(Ue,null)))),r.a.createElement(Me.a,{component:he.a,to:"/get-started",variant:"contained",color:"primary",className:t.buttonStyle},"Get Started"))},va=Object(Te.createMuiTheme)(),_a=va.breakpoints,Ta=Object(v.a)({},va,{overrides:{MuiTypography:{h4:Object(I.a)({fontSize:"2rem"},_a.down("xs"),{fontSize:"1.4rem"})}}}),Ca=function(e){function t(){var e,a;Object(Y.a)(this,t);for(var n=arguments.length,r=new Array(n),o=0;oe.topics.length){var t=this.props.topics.filter(function(e){return!e.parentTopic});this.setState({openTopicList:Array.apply(null,Array(t.length)).map(function(){return!1})})}}},{key:"render",value:function(){var e=this,t=this.props,a=t.classes,n=t.topics,o=n.filter(function(e){return!e.parentTopic}),i=n.filter(function(e){return e.parentTopic});return r.a.createElement("div",{className:a.root},r.a.createElement(ja,null),r.a.createElement(Re.a,{smUp:!0},r.a.createElement(Pt,null)),r.a.createElement(ot.a,{variant:"h5",className:a.title},"Search Categories"),r.a.createElement(Ve.a,null,r.a.createElement(Kt.a,{button:!0,key:"all",onClick:function(){return e.handleClick("all",void 0)}},r.a.createElement(Ua.a,{primary:"Home"})),r.a.createElement(kt.a,null),r.a.createElement(ka,{parentTopics:o,childTopics:i,open:this.state.openTopicList,handleTopClick:this.handleTopClick,handleClick:this.handleClick})))}}]),t}(n.Component),Ga={searchEnvironmentsByTopic:function(e,t){return function(a){a({type:d.CATEGORIZE_ENVIRONMENTS,payload:e}),ne.searchEnvironmentsByTopic(e).then(function(n){a({type:d.CATEGORIZE_ENVIRONMENTS_SUCCESS,environment:n.data,topic:{id:e,name:t}})}).catch(function(e){a({type:d.CATEGORIZE_ENVIRONMENTS_FAILURE}),re(e)})}},resetCategorizedEnvironments:function(){return function(e){e({type:d.CATEGORIZE_ENVIRONMENTS_RESET})}}},xa=Object(c.d)(Object(s.b)(null,Ga),Object(Te.withStyles)(function(e){return{root:{width:240,flexShrink:0,paddingTop:"0",paddingBottom:"0"},title:{margin:2*e.spacing.unit,marginTop:6*e.spacing.unit}}}))(Da),Fa=function(e){function t(e){var a;return Object(Y.a)(this,t),(a=Object(K.a)(this,Object(Z.a)(t).call(this,e))).toggleDrawer=function(e){return function(t){(!t||"keydown"!==t.type||"Tab"!==t.key&&"Shift"!==t.key)&&a.setState({openDrawer:e})}},a.state={openDrawer:!1},a}return Object(J.a)(t,e),Object(q.a)(t,[{key:"render",value:function(){var e=this.props,t=e.classes,a=e.topics;return r.a.createElement("div",null,r.a.createElement(Re.a,{mdDown:!0},r.a.createElement(Fe.a,{className:t.drawerPaper},r.a.createElement(xa,{topics:a}))),r.a.createElement(Re.a,{lgUp:!0},r.a.createElement(Na.a,{open:this.state.openDrawer,onClose:this.toggleDrawer(!1),onOpen:this.toggleDrawer(!0)},r.a.createElement(xa,{topics:a}))))}}]),t}(n.Component),Pa=Object(c.d)(Object(s.b)(function(e){return{topics:e.topics.topics}}),Object(Te.withStyles)(function(){return{drawerPaper:{width:240,flexShrink:0,paddingTop:"0",paddingBottom:"0",height:"100%"}}}))(Fa),Ba=function(e){var t=e.classes;return r.a.createElement("div",{className:t.cardStyle},r.a.createElement("div",{className:t.contentStyle},r.a.createElement(ot.a,{variant:"h6",className:t.titleStyle},"Science Problems packaged as APIs"),r.a.createElement(ot.a,{variant:"subtitle1"},"In Reinforcement Learning an agent interacts with an environment to achieve some goal. Our environments encode problems in science packaged as APIs.")),r.a.createElement("div",{className:t.mediaStyle},r.a.createElement("img",{className:t.imgStyleAPI,src:b.STATIC_URL.concat(b.SCIENCE_API),alt:""})),r.a.createElement(Re.a,{smUp:!0},r.a.createElement(kt.a,null)))},Wa=function(e){var t=e.classes;return r.a.createElement("div",{className:t.cardStyle},r.a.createElement("div",{className:t.contentStyle},r.a.createElement(ot.a,{variant:"h6",className:t.titleStyle},"Reinforcement Learning for Science"),r.a.createElement(ot.a,{variant:"subtitle1"},"SciGym is a resource for facilitating the development of reinforcement learning based solutions to problems in physics and other sciences.")),r.a.createElement("div",{className:t.mediaStyle},r.a.createElement("img",{className:t.imgStyleRL,src:b.STATIC_URL.concat(b.SCIENCE_RL),alt:""})),r.a.createElement(Re.a,{smUp:!0},r.a.createElement(kt.a,null)))},Va=function(e){var t=e.classes;return r.a.createElement("div",{className:t.cardStyle},r.a.createElement("div",{className:t.contentStyle},r.a.createElement(ot.a,{variant:"h6",className:t.titleStyle},"Connecting Computer Science and other Disciplines"),r.a.createElement(ot.a,{variant:"subtitle1"},"SciGym is an attempt to stimulate an open and meaningful exchange between computer scientists and researchers in other disciplines.")),r.a.createElement("div",{className:t.mediaStyle},r.a.createElement("img",{className:t.imgStyleConnect,src:b.STATIC_URL.concat(b.SCIENCE_CPU),alt:""})))},Ha=function(e){function t(){return Object(Y.a)(this,t),Object(K.a)(this,Object(Z.a)(t).apply(this,arguments))}return Object(J.a)(t,e),Object(q.a)(t,[{key:"render",value:function(){var e=this.props.classes;return r.a.createElement(Yt.a,{container:!0,spacing:32,justify:"flex-start"},r.a.createElement(Yt.a,{key:"1",item:!0},r.a.createElement(Ba,{classes:e})),r.a.createElement(Yt.a,{key:"2",item:!0},r.a.createElement(Wa,{classes:e})),r.a.createElement(Yt.a,{key:"3",item:!0},r.a.createElement(Va,{classes:e})))}}]),t}(n.Component),za=Object(Te.withStyles)(function(e){return{cardStyle:{width:"300px",marginLeft:2*e.spacing.unit,marginRight:2*e.spacing.unit},mediaStyle:{textAlign:"center",marginTop:e.spacing.unit,height:"160px"},contentStyle:{height:"170"},titleStyle:{marginTop:e.spacing.unit,marginBottom:e.spacing.unit},imgStyleAPI:{height:"120px",width:"152px"},imgStyleRL:{paddingTop:"10px",height:"140px",width:"140px"},imgStyleConnect:{paddingTop:"10px",height:"140px",width:"127px"}}})(Ha),Ya=10,qa=function(e){function t(e){var a;return Object(Y.a)(this,t),(a=Object(K.a)(this,Object(Z.a)(t).call(this,e))).handleExpandMore=function(){a.setState({visibleEnvironmentCount:a.state.visibleEnvironmentCount+Ya})},a.handleExpandLess=function(){a.setState({visibleEnvironmentCount:a.state.visibleEnvironmentCount-Ya})},a.state={visibleEnvironmentCount:Ya},a}return Object(J.a)(t,e),Object(q.a)(t,[{key:"render",value:function(){var e=this.props.classes,t=this.props.categorizedEnvironments?this.props.categorizedEnvironments:this.props.environments,a=0===t.length,n=this.state.visibleEnvironmentCount>=t.length,o=this.state.visibleEnvironmentCount<=Ya,i=t.slice(0,this.state.visibleEnvironmentCount);return r.a.createElement("div",{className:e.root},r.a.createElement(Ra,null),r.a.createElement("div",{className:e.wrapper},r.a.createElement(Pa,null),r.a.createElement(Yt.a,{container:!0,justify:"center",className:e.gridStyle},r.a.createElement("div",null,!this.props.categorizedEnvironments&&r.a.createElement("div",null,r.a.createElement(ot.a,{variant:"h4",className:e.title},"Features & Goals"),r.a.createElement(za,null)),r.a.createElement(ot.a,{variant:"h4",className:e.title},this.title),a&&r.a.createElement("div",{className:e.emptyStyle},r.a.createElement(ot.a,{variant:"h6",className:e.title},"No environments found")),!a&&r.a.createElement(Ht.a,null,i.map(function(e){return r.a.createElement(r.a.Fragment,{key:e.id},r.a.createElement(ga,{key:e.id,environment:e}),r.a.createElement(kt.a,null))})),r.a.createElement(jt,{classes:e,allEnvVisible:n,noEnvVisible:o,handleExpandMore:this.handleExpandMore,handleExpandLess:this.handleExpandLess})))))}},{key:"title",get:function(){return this.props.categorizedEnvironments?this.props.searchedTopic.name:"Recent environments"}}]),t}(n.Component),Ka=Object(c.d)(Object(s.b)(function(e){return{repositories:e.repositories.repositories,environments:e.environments.environments,categorizedEnvironments:e.environments.categorizedEnvironments,searchedTopic:e.environments.searchedTopic}}),Object(Te.withStyles)(function(e){var t,a;return{root:{flexGrow:1,backgroundColor:"AliceBlue"},title:{margin:2*e.spacing.unit,marginTop:6*e.spacing.unit},wrapper:{display:"flex",flexFlow:"row nowrap"},gridStyle:Object(I.a)({},e.breakpoints.up("lg"),{width:"80%"}),emptyStyle:(t={minWidth:"770px"},Object(I.a)(t,e.breakpoints.down("xs"),{minWidth:"0px"}),Object(I.a)(t,e.breakpoints.up("md"),{minWidth:"950px"}),t),buttonStyle:{left:"40%"},buttonPos:(a={minWidth:"770px"},Object(I.a)(a,e.breakpoints.down("xs"),{minWidth:"0px"}),Object(I.a)(a,e.breakpoints.up("md"),{minWidth:"950px"}),a)}}))(qa),Za=a(44),Ja=a.n(Za),Xa=a(202),Qa=a.n(Xa),$a=Object(c.d)(Object(s.b)(function(e){return{user:e.user,isUpdating:M(e.display,d.UPDATE_USER_PROFILE),errors:G(e.errors,d.UPDATE_USER_PROFILE)}},{updateMyProfile:function(e){return function(t){t({type:d.UPDATE_USER_PROFILE,payload:null}),ne.updateMe(e).then(function(e){t({type:d.UPDATE_USER_PROFILE_SUCCESS,payload:e.data})}).catch(function(e){t({type:d.UPDATE_USER_PROFILE_FAILURE,payload:e}),re(e)})}}}),Object(Te.withStyles)(function(e){return{root:{display:"flex",flexFlow:"column nowrap",paddingBottom:4*e.spacing.unit},textField:{width:300},button:{marginTop:e.spacing.unit,width:250},rightIcon:{marginLeft:e.spacing.unit}}}))(function(e){var t=e.classes,a=e.user,o=e.updateMyProfile,i=e.isUpdating,s=e.errors,c=Object(n.useState)(a.email),l=Object(je.a)(c,2),u=l[0],E=l[1],m=Object(n.useState)(a.firstName),d=Object(je.a)(m,2),p=d[0],h=d[1],g=Object(n.useState)(a.lastName),S=Object(je.a)(g,2),f=S[0],b=S[1];return r.a.createElement("div",{className:t.root},r.a.createElement(Ja.a,{id:"user-name",label:"Username",className:t.textField,margin:"normal",value:a.username,disabled:!0,error:s&&s.username}),r.a.createElement(Ja.a,{id:"email",label:"Email",className:t.textField,margin:"normal",value:u,onChange:function(e){return E(e.target.value)},error:Boolean(s&&s.email),helperText:s&&s.email}),r.a.createElement(Ja.a,{id:"first-name",label:"First Name",className:t.textField,margin:"normal",value:p,onChange:function(e){return h(e.target.value)},error:Boolean(s&&s.firstName),helperText:s&&s.firstName}),r.a.createElement(Ja.a,{id:"last-name",label:"Last Name",className:t.textField,margin:"normal",value:f,onChange:function(e){return b(e.target.value)},error:Boolean(s&&s.lastName),helperText:s&&s.lastName}),r.a.createElement(Me.a,{variant:"contained",color:"primary",className:t.button,onClick:function(){return o({email:u,firstName:p,lastName:f})}},i?"Saving...":"Save",r.a.createElement(Qa.a,{className:t.rightIcon})))}),en=a(61),tn=a.n(en),an=Object(s.b)(function(e){return{userName:e.user.username}},{deleteUser:function(){return function(e){e({type:d.DELETE_USER,payload:null}),ne.deleteMe().then(function(t){e({type:d.DELETE_USER_SUCCESS,payload:t.data})}).catch(function(t){e({type:d.DELETE_USER_FAILURE,payload:t}),re(t)})}}})(function(e){var t=e.userName,a=e.open,o=e.handleClose,i=e.deleteUser,s=Object(n.useState)(""),c=Object(je.a)(s,2),l=c[0],u=c[1],E=l===t;return r.a.createElement(Xe.a,{open:a,onClose:o,"aria-labelledby":"account-delete-title"},r.a.createElement(Ze.a,{id:"account-delete-title"},"Delete Account Confirmation"),r.a.createElement(tt.a,null,r.a.createElement(nt.a,null,"Confirm account deletion by entering your username below"),r.a.createElement(Ja.a,{autoFocus:!0,margin:"dense",id:"name",label:"username",fullWidth:!0,value:l,onChange:function(e){return u(e.target.value)}})),r.a.createElement($e.a,null,r.a.createElement(Me.a,{onClick:o,color:"primary"},"Cancel"),r.a.createElement(Me.a,{onClick:function(){i(),o()},color:"secondary",disabled:!E},"Delete")))}),nn=Object(Te.withStyles)(function(e){return{root:{paddingTop:4*e.spacing.unit,paddingBottom:2*e.spacing.unit},button:{marginTop:e.spacing.unit,width:250},rightIcon:{marginLeft:e.spacing.unit}}})(function(e){var t=e.classes,a=Object(n.useState)(!1),o=Object(je.a)(a,2),i=o[0],s=o[1];return r.a.createElement("div",{className:t.root},r.a.createElement(an,{open:i,handleClose:function(){return s(!1)}}),r.a.createElement(ot.a,{variant:"h4",component:"h3"},"Delete Account"),r.a.createElement(ot.a,{component:"p"},"This action will permanently delete your account. This cannot be undone!"),r.a.createElement(Me.a,{variant:"contained",color:"secondary",className:t.button,onClick:function(){return s(!0)}},"Delete my account",r.a.createElement(tn.a,{className:t.rightIcon})))});var rn=Object(Te.withStyles)(function(e){return{root:Object(v.a)({},e.mixins.gutters(),{paddingTop:2*e.spacing.unit,paddingBottom:2*e.spacing.unit,marginLeft:2*e.spacing.unit})}})(function(e){var t=e.classes;return r.a.createElement("div",{className:t.root},r.a.createElement(ot.a,{variant:"h4",component:"h3"},"Update Account Details"),r.a.createElement($a,null),r.a.createElement(kt.a,null),r.a.createElement(nn,null))}),on=a(152),sn=a.n(on),cn=a(154),ln=a.n(cn),un=a(151),En=a.n(un),mn=a(153),dn=a.n(mn),pn=a(123),hn=a.n(pn);var gn=Object(Te.withStyles)(function(e){return{menuItem:{"&:focus":{backgroundColor:"#82B1FF"}}}})(function(e){var t=e.classes,a=e.to,n=e.icon,o=e.text;return r.a.createElement(ze.a,{className:t.menuItem,component:he.a,to:a},r.a.createElement(hn.a,{className:t.icon},n),r.a.createElement(Ua.a,{classes:{primary:t.primary},inset:!0,primary:o}))});var Sn=Object(Te.withStyles)(function(e){return{drawerPaper:{width:240,flexShrink:0,paddingTop:"0",paddingBottom:"0",height:"100%"}}})(function(e){var t=e.classes,a=Object(n.useState)(!1),o=Object(je.a)(a,2),i=o[0],s=o[1],c=function(e){return function(t){(!t||"keydown"!==t.type||"Tab"!==t.key&&"Shift"!==t.key)&&s(e)}};return r.a.createElement("div",null,r.a.createElement(Re.a,{smDown:!0},r.a.createElement(Fe.a,{className:t.drawerPaper},r.a.createElement(ja,null),r.a.createElement(Ve.a,null,r.a.createElement(gn,{to:"/profile",text:"Repositories",icon:r.a.createElement(En.a,null)}),r.a.createElement(kt.a,null),r.a.createElement(gn,{to:"/profile/account",text:"Account",icon:r.a.createElement(sn.a,null)}),r.a.createElement(gn,{to:"/profile/images",text:"Images",icon:r.a.createElement(dn.a,null)}),r.a.createElement(gn,{to:"/profile/messageboards",text:"Message Boards",icon:r.a.createElement(ln.a,null)})))),r.a.createElement(Re.a,{mdUp:!0},r.a.createElement(Na.a,{open:i,onClose:c(!1),onOpen:c(!0)},r.a.createElement("div",{className:t.drawerPaper},r.a.createElement(ja,null),r.a.createElement(Ve.a,null,r.a.createElement(gn,{to:"/profile",text:"Repositories",icon:r.a.createElement(En.a,null)}),r.a.createElement(kt.a,null),r.a.createElement(gn,{to:"/profile/account",text:"Account",icon:r.a.createElement(sn.a,null)}),r.a.createElement(gn,{to:"/profile/images",text:"Images",icon:r.a.createElement(dn.a,null)}),r.a.createElement(gn,{to:"/profile/messageboards",text:"Message Boards",icon:r.a.createElement(ln.a,null)}))))))}),fn=a(203),bn=a.n(fn),yn=a(49),On=a.n(yn),vn=a(105),_n=a.n(vn),Tn=function(e){function t(e){var a;return Object(Y.a)(this,t),(a=Object(K.a)(this,Object(Z.a)(t).call(this,e))).handleChange=function(e){return function(t){"default"!==t.target.value?(a.setState({selectedAvatar:e}),a.props.handleSelect(e)):(a.setState({selectedAvatar:null}),a.props.handleSelect(null))}},a.state={selectedAvatar:e.avatar},a.handleChange=a.handleChange.bind(Object(ut.a)(Object(ut.a)(a))),a}return Object(J.a)(t,e),Object(q.a)(t,[{key:"render",value:function(){var e=this,t=this.props,a=t.classes,n=t.userImages;return n.length>0?r.a.createElement("div",{className:a.root},r.a.createElement(Yt.a,{container:!0,direction:"row",justify:"flex-start",alignItems:"center",spacing:16,className:a.gridStyle},r.a.createElement(Yt.a,{item:!0,key:"default"},r.a.createElement(Jt.a,{className:a.cardStyle},r.a.createElement(On.a,{className:a.mediaStyle,image:b.STATIC_URL.concat(b.SCIGYM_LOGO)},r.a.createElement(_n.a,{checked:null===this.state.selectedAvatar,onChange:this.handleChange(null),value:"default",name:"radio-button-image","aria-label":"default",className:a.radioStyle})))),n.map(function(t){return r.a.createElement(Yt.a,{item:!0,key:t.id},r.a.createElement(Jt.a,{className:a.cardStyle},r.a.createElement(On.a,{className:a.mediaStyle,image:b.MEDIA_URL.concat(t.filePath)},r.a.createElement(_n.a,{checked:null!==e.state.selectedAvatar&&e.state.selectedAvatar.id===t.id,onChange:e.handleChange(t),value:t.id,name:"radio-image-button","aria-label":t.id,className:a.radioStyle}))))}))):r.a.createElement(ot.a,{className:a.textStyle,variant:"subtitle1"},"You don't have any images, yet!")}}]),t}(n.Component),Cn=Object(Te.withStyles)(function(e){var t;return{root:(t={margin:"auto",height:"155px",paddingLeft:"5%",paddingRight:"5%",backgroundColor:"AliceBlue",paddingTop:3*e.spacing.unit,maxWidth:"500px"},Object(I.a)(t,e.breakpoints.down("sm"),{paddingLeft:"5%",paddingRight:"5%"}),Object(I.a)(t,e.breakpoints.up("lg"),{paddingLeft:"5%",paddingRight:"5%"}),t),gridStyle:{overflow:"auto",overflowX:"scroll",flexWrap:"nowrap",transform:"translateZ(0)"},cardStyle:{width:"100px"},mediaStyle:{height:"100px"},textStyle:{margin:e.spacing.unit}}})(Tn),Rn=2e6,In=function(e){function t(e){var a;Object(Y.a)(this,t),(a=Object(K.a)(this,Object(Z.a)(t).call(this,e))).handleClick=function(e){a.setState({anchorEl:e.currentTarget})},a.handleClose=function(){a.setState({anchorEl:null})},a.handleSelect=function(e){a.props.handleSelect(e)},a.handleChange=function(e){return function(t){t.preventDefault();var n,r=t.target.files[0],o=r.name.split(".").pop().toLowerCase();console.log(r.size),r.size>Rn?n="Sorry, avatar size is limited to 2MB.":e.includes(".".concat(o))||(n="Incorrect file extension. we only accept ".concat(e.join())),n?a.setState({error:n}):a.props.createImage(r)}};var n=b.SCIGYM_LOGO;return null!=e.avatar&&(n=e.avatar.filePath),a.state={avatar:e.avatar,avatarURL:b.MEDIA_URL.concat(n),error:null,anchorEl:null},a.handleChange=a.handleChange.bind(Object(ut.a)(Object(ut.a)(a))),a}return Object(J.a)(t,e),Object(q.a)(t,[{key:"componentDidUpdate",value:function(e){if(e.avatar!==this.props.avatar){var t=b.SCIGYM_LOGO;null!=this.props.avatar&&(t=this.props.avatar.filePath),this.setState({avatar:this.props.avatar,avatarURL:b.MEDIA_URL.concat(t),error:null})}}},{key:"render",value:function(){var e=this.state,t=e.error,a=e.anchorEl,n=this.props,o=n.classes,i=n.userImages,s=n.imageConfig,c=n.loading,l=Boolean(a);return r.a.createElement("div",{className:o.root},r.a.createElement("div",{className:o.logoStyle},c?r.a.createElement(te.a,{className:o.loadingStyle,size:50,disableShrink:!0,color:"primary"}):r.a.createElement("img",{src:this.state.avatarURL,height:"150",width:"150",alt:""})),r.a.createElement("div",{className:o.contentStyle},r.a.createElement(Me.a,{color:"primary","aria-owns":l?"simple-popper":void 0,"aria-haspopup":"true",variant:"contained",onClick:this.handleClick},"My Images"),r.a.createElement(ot.a,{className:o.imageUploadText,variant:"h6"},"Upload new image"),r.a.createElement("input",{type:"file",onChange:this.handleChange(s)})),r.a.createElement(ya.a,{id:"userImages-popper",open:l,anchorEl:a,onClose:this.handleClose,anchorOrigin:{vertical:"bottom",horizontal:"left"},transformOrigin:{vertical:"top",horizontal:"left"}},r.a.createElement(Cn,{userImages:i,avatar:this.state.avatar,handleSelect:this.handleSelect})),t?r.a.createElement(ot.a,{variant:"subtitle1",color:"error",className:o.errorStyle},t):null)}}]),t}(n.Component),Nn={createImage:function(){for(var e=arguments.length,t=new Array(e),a=0;a0&&r.a.createElement(Ht.a,null,c.map(function(t){return r.a.createElement(ra.a,{label:t,key:t,onDelete:e.handleDeleteTag(t),className:a.tagStyle,color:"primary",variant:"outlined"})})),s?r.a.createElement(te.a,{className:a.loadingStyle,size:30,disableShrink:!0,color:"primary"}):r.a.createElement(Me.a,{onClick:this.handleSubmit},"Submit")))}}]),t}(n.Component),qn={getUserImages:ue,createEnvironment:function(){for(var e=arguments.length,t=new Array(e),a=0;a0?r.a.createElement("div",{className:t.root},r.a.createElement(ot.a,{variant:"h4",component:"h3"},"My Images"),r.a.createElement(ot.a,{component:"subtitle1"},"You can upload more images when creating environments from your repositories."),r.a.createElement(Yt.a,{container:!0,className:t.rootGrid,justify:"flex-start",alignItems:"flex-start",spacing:16},a.map(function(e){return r.a.createElement(Yt.a,{key:e.id,item:!0},r.a.createElement(hr,{classes:t,image:e}))}))):r.a.createElement("div",{className:t.root},r.a.createElement(ot.a,{variant:"h4",component:"h3"},"My Images"),r.a.createElement(ot.a,{variant:"subtitle1",component:"h5"},"You don't have any images!"),r.a.createElement(ot.a,{variant:"subtitle2",component:"h6"},r.a.createElement(Er.a,{className:t.iconStyle}),"Images can be uploaded when you are creating environments from your repositories."))}}]),t}(n.Component),Sr={getUserImages:ue},fr=Object(c.d)(Object(s.b)(function(e){return{userImages:e.images.userImages}},Sr),Object(Te.withStyles)(function(e){return{root:Object(v.a)({},e.mixins.gutters(),{paddingTop:2*e.spacing.unit,paddingBottom:3*e.spacing.unit,marginLeft:2*e.spacing.unit,maxWidth:"600px"}),cardStyle:{width:"200px"},mediaStyle:{height:"200px"},rootGrid:{paddingTop:e.spacing.unit,flexGrow:1},buttonStyle:{margin:e.spacing.unit},iconStyle:{marginRight:e.spacing.unit}}}))(gr),br=Object(Te.withStyles)(function(e){return{root:{flex:"1"}}})(function(e){var t=e.classes;return r.a.createElement(Yt.a,{container:!0,className:t.root},r.a.createElement(Yt.a,{item:!0,sm:7,xs:9},r.a.createElement(Kt.a,null,r.a.createElement(ot.a,{variant:"subtitle1"},"My Message Boards"))),r.a.createElement(Re.a,{xsDown:!0},r.a.createElement(Yt.a,{item:!0,sm:3},r.a.createElement(Kt.a,null,r.a.createElement(ot.a,{variant:"subtitle1"},"Tags")))),r.a.createElement(Yt.a,{item:!0,sm:2,xs:3},r.a.createElement(Kt.a,null,r.a.createElement(ot.a,{variant:"subtitle1"},"Actions"))))}),yr=a(97),Or=a.n(yr),vr=a(131),_r=a.n(vr),Tr=Object(Te.withStyles)(function(e){return{root:{width:"100%"},child:{width:"auto",margin:2*e.spacing.unit},textField:{width:"100%"}}})(function(e){var t=e.classes,a=e.title,n=e.description,o=e.handleChange,i=e.errors;return r.a.createElement("div",{className:t.root},r.a.createElement("div",{className:t.child},r.a.createElement(Ja.a,{id:"filled-title",label:"Title",className:t.textField,value:a,onChange:o("title"),margin:"normal",variant:"filled",error:Boolean(i&&i.title),helperText:i&&i.title})),r.a.createElement("div",{className:t.child},r.a.createElement(Ja.a,{id:"filled-full-description",label:"Leave a comment",className:t.textField,value:Boolean(n)?n:"",onChange:o("description"),multiline:!0,rows:10,margin:"normal",variant:"filled",error:Boolean(i&&i.description),helperText:i?i.description:"We support styling with markdown."})))}),Cr=Object(Te.withStyles)(function(e){return{root:{width:"100%"},child:{width:"auto",margin:2*e.spacing.unit},textField:{width:"100%"}}})(function(e){var t=e.classes,a=e.tag,n=e.handleChange,o=e.handleAddTag,i=e.errors;return r.a.createElement("div",{className:t.root},r.a.createElement("div",{className:t.child},r.a.createElement(wn.a,{className:t.textField},r.a.createElement(Pn.a,{htmlFor:"adornment-tag"},"Add a Tag"),r.a.createElement(xn.a,{id:"adornment-tag",value:a,onChange:n("tag"),endAdornment:r.a.createElement(Wn.a,{position:"end"},r.a.createElement(be.a,{"aria-label":"Add tag",onClick:o},r.a.createElement(Hn.a,null))),error:Boolean(i&&i.tags)}),r.a.createElement(Ln.a,{error:!0,id:"tag-helper-text"},i&&i.tags))))}),Rr=function(e){function t(e){var a;Object(Y.a)(this,t),(a=Object(K.a)(this,Object(Z.a)(t).call(this,e))).handleSubmit=function(e){e.preventDefault(),Boolean(a.props.messageboard)?a.props.editMessageBoard(a.state):a.props.createMessageBoard(a.state)},a.handleChange=function(e){return function(t){t.preventDefault(),a.setState(Object(I.a)({},e,t.target.value))}},a.handleAddTag=function(){var e=a.state.tags;e.includes(a.state.tag)||""===a.state.tag||a.setState({tags:e.concat([a.state.tag]),tag:""})},a.handleDeleteTag=function(e){return function(t){t.preventDefault();var n=a.state.tags;n.splice(n.indexOf(e),1),a.setState({tag:"",tags:n})}};var n=e.environment,r=e.messageboard,o=Boolean(n),i=Boolean(r);return a.state={id:i?r.id:void 0,environment:o?n.id:void 0,title:i?r.title:"",description:i?r.description:"",tag:"",tags:i&&Boolean(r.tags)?r.tags:[]},a.handleSubmit=a.handleSubmit.bind(Object(ut.a)(Object(ut.a)(a))),a.handleChange=a.handleChange.bind(Object(ut.a)(Object(ut.a)(a))),a.handleAddTag=a.handleAddTag.bind(Object(ut.a)(Object(ut.a)(a))),a.handleDeleteTag=a.handleDeleteTag.bind(Object(ut.a)(Object(ut.a)(a))),a}return Object(J.a)(t,e),Object(q.a)(t,[{key:"componentDidUpdate",value:function(e){e.uploadSuccess!==this.props.uploadSuccess&&this.props.uploadSuccess&&(this.props.onClose(),this.props.resetMessageBoardsProps()),void 0===this.state.environment&&Boolean(this.props.environment)&&this.setState({environment:this.props.environment.id})}},{key:"render",value:function(){var e=this,t=this.props,a=t.classes,n=t.errors,o=t.loading,i=this.state.tags;return r.a.createElement("form",{className:a.container},r.a.createElement(Xe.a,{onClose:this.props.onClose,open:this.props.open,fullWidth:!0},r.a.createElement(Ze.a,null,"Start a Discussion"),r.a.createElement(Tr,{title:this.state.title,description:this.state.description,handleChange:this.handleChange,errors:n}),r.a.createElement(Cr,{tag:this.state.tag,handleChange:this.handleChange,handleAddTag:this.handleAddTag,errors:n}),i.length>0&&r.a.createElement(Ht.a,null,i.map(function(t){return r.a.createElement(ra.a,{label:t,key:t,onDelete:e.handleDeleteTag(t),className:a.tagStyle,color:"primary",variant:"outlined"})})),o?r.a.createElement(te.a,{className:a.loadingStyle,size:30,disableShrink:!0,color:"primary"}):r.a.createElement(Me.a,{onClick:this.handleSubmit},"Submit")))}}]),t}(n.Component);var Ir={createMessageBoard:vt,resetMessageBoardsProps:_t,editMessageBoard:function(){for(var e=arguments.length,t=new Array(e),a=0;a20?e.substring(0,17)+"...":e;return r.a.createElement(ra.a,{label:a,key:e,size:"small",className:t.tagStyle,color:"primary"})}))))),r.a.createElement(Yt.a,{item:!0,sm:2,xs:3},r.a.createElement(Kt.a,null,r.a.createElement(wr,{classes:t,messageboard:a}))))}}]),t}(n.Component),Lr=Object(Te.withStyles)(function(e){return{root:{flex:"1"},tagStyle:Object(I.a)({margin:.2*e.spacing.unit},e.breakpoints.down("xs"),{margin:"0"}),buttonStyle:Object(I.a)({margin:.2*e.spacing.unit},e.breakpoints.down("xs"),{margin:.1*e.spacing.unit})}})(Mr),kr=function(e){function t(){return Object(Y.a)(this,t),Object(K.a)(this,Object(Z.a)(t).apply(this,arguments))}return Object(J.a)(t,e),Object(q.a)(t,[{key:"render",value:function(){var e=this.props,t=e.classes,a=e.messageboards;return r.a.createElement("div",{className:t.root},a.length>0?r.a.createElement(Jt.a,null,r.a.createElement(Ht.a,null,r.a.createElement(br,null),r.a.createElement(kt.a,null),a.map(function(e){return r.a.createElement(r.a.Fragment,{key:e.id},r.a.createElement(Lr,{key:e.id,messageboard:e}),r.a.createElement(kt.a,null))}))):r.a.createElement("div",null,r.a.createElement(ot.a,{variant:"h4",component:"h3"},"My Message Boards"),r.a.createElement(ot.a,{variant:"subtitle1",component:"h6"},"You haven't openend any discussions yet!")))}}]),t}(n.Component);var Dr=Object(c.d)(Object(s.b)(function(e){var t=e.messageboards,a=t.messageboards,n=t.num_comments,r=e.user.id;return{messageboards:a.filter(function(e){return e.author.id===r}),replies:n}}),Object(Te.withStyles)(function(e){return{root:Object(v.a)({},e.mixins.gutters(),{paddingTop:2*e.spacing.unit,paddingBottom:3*e.spacing.unit,marginLeft:2*e.spacing.unit,width:"100%"})}}))(kr),Gr=Object(c.d)(Object(s.b)(function(e){return{userExists:e.user.exists}}),Object(Te.withStyles)(function(e){return{root:{display:"flex",flexFlow:"row nowrap",height:"100%"}}}))(function(e){var t=e.classes;return e.userExists?r.a.createElement("div",{className:t.root},r.a.createElement(Sn,null),r.a.createElement(Q.a,null,r.a.createElement(X.a,{path:"/profile/account",component:rn}),r.a.createElement(X.a,{path:"/profile/messageboards",component:Dr}),r.a.createElement(X.a,{path:"/profile/images",component:fr}),r.a.createElement(X.a,{path:"*",component:lr}))):r.a.createElement(ae.a,{to:"/"})}),xr=a(205),Fr=a(124),Pr=a.n(Fr),Br=a(106),Wr=a.n(Br),Vr=a(156),Hr=a(208),zr=function(e){return{listItem:{marginTop:e.spacing.unit},blockquoteStyle:{margin:e.spacing.unit,borderLeft:"2px solid #ccc",paddingLeft:e.spacing.unit}}},Yr={overrides:{pre:{props:{style:{backgroundColor:"#f5f5f5",padding:"10px",overflowX:"auto"}}},code:{props:{style:{backgroundColor:"#f5f5f5",padding:"3px"}}},h1:{component:function(e){return r.a.createElement(ot.a,Object.assign({gutterBottom:!0,variant:"h4"},e))}},h2:{component:function(e){return r.a.createElement(ot.a,Object.assign({gutterBottom:!0,variant:"h6"},e))}},h3:{component:function(e){return r.a.createElement(ot.a,Object.assign({gutterBottom:!0,variant:"subtitle1"},e))}},h4:{component:function(e){return r.a.createElement(ot.a,Object.assign({gutterBottom:!0,variant:"caption",paragraph:!0},e))}},p:{component:function(e){return r.a.createElement(ot.a,Object.assign({paragraph:!0},e))}},a:{component:function(e){return r.a.createElement("a",{href:e.href,target:"_blank",rel:"noopener noreferrer"},e.children)}},blockquote:{component:Object(Te.withStyles)(zr)(function(e){var t=e.classes,a=Object(Vr.a)(e,["classes"]);return r.a.createElement("blockquote",{className:t.blockquoteStyle},a.children)})},li:{component:Object(Te.withStyles)(zr)(function(e){var t=e.classes,a=Object(Vr.a)(e,["classes"]);return r.a.createElement("li",{className:t.listItem},r.a.createElement(ot.a,Object.assign({component:"span"},a)))})}}};var qr=function(e){return r.a.createElement(Hr.a,Object.assign({options:Yr},e))},Kr=function(e){function t(){var e,a;Object(Y.a)(this,t);for(var n=arguments.length,r=new Array(n),o=0;o20?e.substring(0,17)+"...":e;return r.a.createElement(ra.a,{label:a,key:e,size:"small",className:t.tagStyle,color:"primary"})}))))),r.a.createElement(Yt.a,{item:!0,sm:2,xs:3},r.a.createElement(Kt.a,null,r.a.createElement(ot.a,{variant:"subtitle1"},n))))}}]),t}(n.Component),ho=Object(Te.withStyles)(function(e){return{root:{flex:"1"},tagStyle:Object(I.a)({margin:.2*e.spacing.unit},e.breakpoints.down("xs"),{margin:"0"})}})(po),go=10,So=function(e){function t(e){var a;return Object(Y.a)(this,t),(a=Object(K.a)(this,Object(Z.a)(t).call(this,e))).handleExpandMore=function(){a.setState({visibleBoardsCount:a.state.visibleBoardsCount+go})},a.handleExpandLess=function(){a.setState({visibleBoardsCount:a.state.visibleBoardsCount-go})},a.handleClickOpenLogin=function(){var e=!a.state.openLogin;a.setState({openLogin:e})},a.handleClickOpenForm=function(){var e=!a.state.openForm;a.setState({openForm:e})},a.handleCloseLogin=function(){a.setState({openLogin:!1})},a.handleCloseForm=function(){a.setState({openForm:!1})},a.state={openLogin:!1,openForm:!1,visibleBoardsCount:go},a}return Object(J.a)(t,e),Object(q.a)(t,[{key:"render",value:function(){var e=this.props,t=e.classes,a=e.environment,n=e.userExists,o=e.messageboards,i=e.replies,s=this.state,c=s.openLogin,l=s.openForm,u=Boolean(a)?"env/".concat(a.name):"",E=this.state.visibleBoardsCount>=o.length,m=this.state.visibleBoardsCount<=go,d=o.slice(0,this.state.visibleBoardsCount);return r.a.createElement("div",{className:t.root},r.a.createElement(Rt.a,{onClick:n?this.handleClickOpenForm:this.handleClickOpenLogin,variant:"contained",color:"primary",className:t.uploadButtonStyle},"New Discussion",r.a.createElement(tr.a,{className:t.iconStyle})),r.a.createElement(Jt.a,null,r.a.createElement(Ht.a,null,r.a.createElement(mo,null),r.a.createElement(kt.a,null),d.map(function(e){return r.a.createElement(r.a.Fragment,{key:e.id},r.a.createElement(ho,{key:e.id,messageboard:e,replies:i[e.id]}),r.a.createElement(kt.a,null))}))),r.a.createElement(jt,{classes:t,allEnvVisible:E,noEnvVisible:m,handleExpandMore:this.handleExpandMore,handleExpandLess:this.handleExpandLess}),r.a.createElement(Nr,{environment:a,open:l,onClose:this.handleCloseForm}),r.a.createElement(lt,{open:c,onClose:this.handleCloseLogin,callbackURL:u}))}}]),t}(n.Component);var fo=Object(c.d)(Object(s.b)(function(e,t){var a=e.messageboards,n=a.messageboards,r=a.num_comments,o=t.env_url;return{messageboards:n.filter(function(e){return e.environment.url===o}),userExists:e.user.exists,replies:r}}),Object(Te.withStyles)(function(e){var t,a;return{uploadButtonStyle:{left:"0px",marginBottom:4*e.spacing.unit},buttonStyle:{left:"40%"},buttonPos:(t={minWidth:"770px"},Object(I.a)(t,e.breakpoints.down("xs"),{minWidth:"0px"}),Object(I.a)(t,e.breakpoints.up("md"),{minWidth:"950px"}),t),iconStyle:{marginLeft:e.spacing.unit},root:(a={marginTop:"40px",margin:"auto",width:"90%"},Object(I.a)(a,e.breakpoints.up("md"),{width:"75%"}),Object(I.a)(a,"paddingBottom","50px"),a)}}))(So),bo=function(e){function t(){return Object(Y.a)(this,t),Object(K.a)(this,Object(Z.a)(t).apply(this,arguments))}return Object(J.a)(t,e),Object(q.a)(t,[{key:"render",value:function(){var e=this.props,t=e.classes,a=e.environment,n=this.props.match.params.env_url,o=b.STATIC_URL+b.SCIGYM_LOGO;if(void 0!==a){var i=this.props.environment.currentAvatar;null!=i&&(o=b.MEDIA_URL.concat(i.filePath))}return r.a.createElement("div",null,void 0===a?r.a.createElement(ot.a,{variant:"h6",className:t.title},"There is no environment here..."):r.a.createElement("div",{className:t.root},r.a.createElement(Yt.a,{container:!0,justify:"center",className:t.gridStyle,spacing:0},r.a.createElement(Yt.a,{key:"image",item:!0,className:t.gridImg},r.a.createElement(Jt.a,{className:t.imgCardStyle},r.a.createElement(On.a,{className:t.mediaStyle,image:o}))),r.a.createElement(Yt.a,{key:"content",item:!0,className:t.gridContent},r.a.createElement(Eo,{environment:a}))),r.a.createElement(fo,{environment:a,env_url:n})))}}]),t}(n.Component);var yo=Object(c.d)(Object(s.b)(function(e,t){var a=e.environments.environments,n=t.match.params.env_url;return{environment:a.find(function(e){return e.url===n})}}),Object(Te.withStyles)(function(e){return{root:{display:"flex",flexFlow:"row wrap",backgroundColor:"AliceBlue"},title:{margin:2*e.spacing.unit,marginTop:6*e.spacing.unit},gridStyle:{paddingTop:"50px"},gridImg:Object(I.a)({width:"300px",margin:e.spacing.unit},e.breakpoints.down("sm"),{width:"200px"}),gridContent:Object(I.a)({width:"600px",margin:e.spacing.unit},e.breakpoints.down("md"),{width:"400px"}),imgCardStyle:Object(I.a)({width:"175px"},e.breakpoints.down("sm"),{width:"150px"}),mediaStyle:Object(I.a)({height:"175px"},e.breakpoints.down("sm"),{height:"150px"})}}))(bo),Oo=Object(Te.withStyles)(function(e){var t;return{textBlock:(t={margin:5*e.spacing.unit,marginRight:10*e.spacing.unit,marginLeft:10*e.spacing.unit},Object(I.a)(t,e.breakpoints.down("sm"),{margin:1*e.spacing.unit,marginRight:2*e.spacing.unit,marginLeft:2*e.spacing.unit}),Object(I.a)(t,e.breakpoints.up("lg"),{margin:5*e.spacing.unit,marginRight:20*e.spacing.unit,marginLeft:20*e.spacing.unit}),t),title:{marginTop:4*e.spacing.unit,marginBottom:e.spacing.unit}}})(function(e){var t=e.classes;return r.a.createElement("div",null,r.a.createElement("div",{className:t.textBlock},r.a.createElement(ot.a,{className:t.title,variant:"h3"},"Privacy policy"),r.a.createElement(ot.a,{variant:"subtitle1"},'This privacy policy ("Policy") describes how Website Operator ("Website Operator", "we", "us" or "our") collects, protects and uses the personally identifiable information ("Personal Information") you ("User", "you" or "your") may provide on the ',r.a.createElement("a",{target:"_blank",rel:"noopener noreferrer",href:"http://www.scigym.ai"},"scigym.ai"),' website and any of its products or services (collectively, "Website" or "Services"). It also describes the choices available to you regarding our use of your Personal Information and how you can access and update this information. This Policy does not apply to the practices of companies that we do not own or control, or to individuals that we do not employ or manage.'),r.a.createElement(ot.a,{className:t.title,variant:"h4"},"Automatic collection of information"),r.a.createElement(ot.a,{variant:"subtitle1"},"When you visit the Website our servers automatically record information that your browser sends. This data may include information such as your device's IP address, browser type and version, operating system type and version, language preferences or the webpage you were visiting before you came to our Website, pages of our Website that you visit, the time spent on those pages, information you search for on our Website, access times and dates, and other statistics."),r.a.createElement(ot.a,{className:t.title,variant:"h4"},"Collection of personal information"),r.a.createElement(ot.a,{variant:"subtitle1"},"You can visit the Website without telling us who you are or revealing any information by which someone could identify you as a specific, identifiable individual. If, however, you wish to use some of the Website's features, you will be asked to provide certain Personal Information (for example, your name and e-mail address). We receive and store any information you knowingly provide to us when you create an account, publish content, or fill any online forms on the Website. When required, this information may include your email address, name, or other Personal Information. You can choose not to provide us with your Personal Information, but then you may not be able to take advantage of some of the Website's features. Users who are uncertain about what information is mandatory are welcome to contact us."),r.a.createElement(ot.a,{className:t.title,variant:"h4"},"Managing personal information"),r.a.createElement(ot.a,{variant:"subtitle1"},"You are able to access, add to, update and delete certain Personal Information about you. The information you can view, update, and delete may change as the Website or Services change. When you update information, however, we may maintain a copy of the unrevised information in our records. Some information may remain in our private records after your deletion of such information from your account. We will retain and use your Personal Information for the period necessary to comply with our legal obligations, resolve disputes, and enforce our agreements unless a longer retention period is required or permitted by law. We may use any aggregated data derived from or incorporating your Personal Information after you update or delete it, but not in a manner that would identify you personally. Once the retention period expires, Personal Information shall be deleted. Therefore, the right to access, the right to erasure, the right to rectification and the right to data portability cannot be enforced after the expiration of the retention period."),r.a.createElement(ot.a,{className:t.title,variant:"h4"},"Use and processing of collected information"),r.a.createElement(ot.a,{variant:"subtitle1"},"Any of the information we collect from you may be used to personalize your experience; improve our Website; improve customer service and respond to queries and emails of our customers; send notification emails such as password reminders, updates, etc; run and operate our Website and Services. Information collected automatically is used only to identify potential cases of abuse and establish statistical information regarding Website usage. This statistical information is not otherwise aggregated in such a way that would identify any particular user of the system."),r.a.createElement(ot.a,{variant:"subtitle1"},"We may process Personal Information related to you if one of the following applies: (i) You have given your consent for one or more specific purposes. Note that under some legislations we may be allowed to process information until you object to such processing (by opting out), without having to rely on consent or any other of the following legal bases below. This, however, does not apply, whenever the processing of Personal Information is subject to European data protection law; (ii) Provision of information is necessary for the performance of an agreement with you and/or for any pre-contractual obligations thereof; (iii) Processing is necessary for compliance with a legal obligation to which you are subject; (iv) Processing is related to a task that is carried out in the public interest or in the exercise of official authority vested in us; (v) Processing is necessary for the purposes of the legitimate interests pursued by us or by a third party. In any case, we will be happy to clarify the specific legal basis that applies to the processing, and in particular whether the provision of Personal Information is a statutory or contractual requirement, or a requirement necessary to enter into a contract."),r.a.createElement(ot.a,{className:t.title,variant:"h4"},"Information transfer and storage"),r.a.createElement(ot.a,{variant:"subtitle1"},"Depending on your location, data transfers may involve transferring and storing your information in a country other than your own. You are entitled to learn about the legal basis of information transfers to a country outside the European Union or to any international organization governed by public international law or set up by two or more countries, such as the UN, and about the security measures taken by us to safeguard your information. If any such transfer takes place, you can find out more by checking the relevant sections of this document or inquire with us using the information provided in the contact section."),r.a.createElement(ot.a,{className:t.title,variant:"h4"},"The rights of users"),r.a.createElement(ot.a,{variant:"subtitle1"},"You may exercise certain rights regarding your information processed by us. In particular, you have the right to do the following: (i) you have the right to withdraw consent where you have previously given your consent to the processing of your information; (ii) you have the right to object to the processing of your information if the processing is carried out on a legal basis other than consent; (iii) you have the right to learn if information is being processed by us, obtain disclosure regarding certain aspects of the processing and obtain a copy of the information undergoing processing; (iv) you have the right to verify the accuracy of your information and ask for it to be updated or corrected; (v) you have the right, under certain circumstances, to restrict the processing of your information, in which case, we will not process your information for any purpose other than storing it; (vi) you have the right, under certain circumstances, to obtain the erasure of your Personal Information from us; (vii) you have the right to receive your information in a structured, commonly used and machine readable format and, if technically feasible, to have it transmitted to another controller without any hindrance. This provision is applicable provided that your information is processed by automated means and that the processing is based on your consent, on a contract which you are part of or on pre-contractual obligations thereof."),r.a.createElement(ot.a,{className:t.title,variant:"h4"},"The right to object to processing"),r.a.createElement(ot.a,{variant:"subtitle1"},"Where Personal Information is processed for the public interest, in the exercise of an official authority vested in us or for the purposes of the legitimate interests pursued by us, you may object to such processing by providing a ground related to your particular situation to justify the objection. You must know that, however, should your Personal Information be processed for direct marketing purposes, you can object to that processing at any time without providing any justification. To learn, whether we are processing Personal Information for direct marketing purposes, you may refer to the relevant sections of this document."),r.a.createElement(ot.a,{className:t.title,variant:"h4"},"How to exercise these rights"),r.a.createElement(ot.a,{variant:"subtitle1"},"Any requests to exercise User rights can be directed to the Owner through the contact details provided in this document. These requests can be exercised free of charge and will be addressed by the Owner as early as possible."),r.a.createElement(ot.a,{className:t.title,variant:"h4"},"Privacy of children"),r.a.createElement(ot.a,{variant:"subtitle1"},"We do not knowingly collect any Personal Information from children under the age of 13. If you are under the age of 13, please do not submit any Personal Information through our Website or Service. We encourage parents and legal guardians to monitor their children's Internet usage and to help enforce this Policy by instructing their children never to provide Personal Information through our Website or Service without their permission. If you have reason to believe that a child under the age of 13 has provided Personal Information to us through our Website or Service, please contact us. You must also be at least 16 years of age to consent to the processing of your Personal Information in your country (in some countries we may allow your parent or guardian to do so on your behalf)."),r.a.createElement(ot.a,{className:t.title,variant:"h4"},"Cookies"),r.a.createElement(ot.a,{variant:"subtitle1"},'The Website uses "cookies" to help personalize your online experience. A cookie is a text file that is placed on your hard disk by a web page server. Cookies cannot be used to run programs or deliver viruses to your computer. Cookies are uniquely assigned to you, and can only be read by a web server in the domain that issued the cookie to you. We may use cookies to collect, store, and track information for statistical purposes to operate our Website and Services. You have the ability to accept or decline cookies. Most web browsers automatically accept cookies, but you can usually modify your browser setting to decline cookies if you prefer. To learn more about cookies and how to manage them, visit ',r.a.createElement("a",{target:"_blank",rel:"noopener noreferrer",href:"https://www.internetcookies.org"},"internetcookies.org")),r.a.createElement(ot.a,{className:t.title,variant:"h4"},"Do Not Track signals"),r.a.createElement(ot.a,{variant:"subtitle1"},"Some browsers incorporate a Do Not Track feature that signals to websites you visit that you do not want to have your online activity tracked. Tracking is not the same as using or collecting information in connection with a website. For these purposes, tracking refers to collecting personally identifiable information from consumers who use or visit a website or online service as they move across different websites over time. How browsers communicate the Do Not Track signal is not yet uniform. As a result, this Website is not yet set up to interpret or respond to Do Not Track signals communicated by your browser. Even so, as described in more detail throughout this Policy, we limit our use and collection of your personal information."),r.a.createElement(ot.a,{className:t.title,variant:"h4"},"Links to other websites"),r.a.createElement(ot.a,{variant:"subtitle1"},"Our Website contains links to other websites that are not owned or controlled by us. Please be aware that we are not responsible for the privacy practices of such other websites or third-parties. We encourage you to be aware when you leave our Website and to read the privacy statements of each and every website that may collect Personal Information."),r.a.createElement(ot.a,{className:t.title,variant:"h4"},"Information security"),r.a.createElement(ot.a,{variant:"subtitle1"},"We secure information you provide on computer servers in a controlled, secure environment, protected from unauthorized access, use, or disclosure. We maintain reasonable administrative, technical, and physical safeguards in an effort to protect against unauthorized access, use, modification, and disclosure of Personal Information in its control and custody. However, no data transmission over the Internet or wireless network can be guaranteed. Therefore, while we strive to protect your Personal Information, you acknowledge that (i) there are security and privacy limitations of the Internet which are beyond our control; (ii) the security, integrity, and privacy of any and all information and data exchanged between you and our Website cannot be guaranteed; and (iii) any such information and data may be viewed or tampered with in transit by a third-party, despite best efforts."),r.a.createElement(ot.a,{className:t.title,variant:"h4"},"Data breach"),r.a.createElement(ot.a,{variant:"subtitle1"},"In the event we become aware that the security of the Website has been compromised or users Personal Information has been disclosed to unrelated third parties as a result of external activity, including, but not limited to, security attacks or fraud, we reserve the right to take reasonably appropriate measures, including, but not limited to, investigation and reporting, as well as notification to and cooperation with law enforcement authorities. In the event of a data breach, we will make reasonable efforts to notify affected individuals if we believe that there is a reasonable risk of harm to the user as a result of the breach or if notice is otherwise required by law. When we do, we will post a notice on the Website."),r.a.createElement(ot.a,{className:t.title,variant:"h4"},"Legal disclosure"),r.a.createElement(ot.a,{variant:"subtitle1"},"We will disclose any information we collect, use or receive if required or permitted by law, such as to comply with a subpoena, or similar legal process, and when we believe in good faith that disclosure is necessary to protect our rights, protect your safety or the safety of others, investigate fraud, or respond to a government request."),r.a.createElement(ot.a,{className:t.title,variant:"h4"},"Changes and amendments"),r.a.createElement(ot.a,{variant:"subtitle1"},"We may update this Privacy Policy from time to time in our discretion and will notify you of any material changes to the way in which we treat Personal Information. When changes are made, we will revise the updated date at the bottom of this page. We may also provide notice to you in other ways in our discretion, such as through contact information you have provided. Any updated version of this Privacy Policy will be effective immediately upon the posting of the revised Privacy Policy unless otherwise specified. Your continued use of the Website or Services after the effective date of the revised Privacy Policy (or such other act specified at that time) will constitute your consent to those changes. However, we will not, without your consent, use your Personal Data in a manner materially different than what was stated at the time your Personal Data was collected. Policy was created with ",r.a.createElement("a",{target:"_blank",rel:"noopener noreferrer",title:"Privacy policy generator",href:"https://www.websitepolicies.com/privacy-policy-generator"},"WebsitePolicies"),"."),r.a.createElement(ot.a,{className:t.title,variant:"h4"},"Acceptance of this policy"),r.a.createElement(ot.a,{variant:"subtitle1"},"You acknowledge that you have read this Policy and agree to all its terms and conditions. By using the Website or its Services you agree to be bound by this Policy. If you do not agree to abide by the terms of this Policy, you are not authorized to use or access the Website and its Services."),r.a.createElement(ot.a,{className:t.title,variant:"h4"},"Contacting us"),r.a.createElement(ot.a,{variant:"subtitle1"},"If you would like to contact us to understand more about this Policy or wish to contact us concerning any matter relating to individual rights and your Personal Information, you may send an email to info@scigym.ai"),r.a.createElement(ot.a,{variant:"subtitle1"},"This document was last zxc@asd updated on August 30, 2019")))}),vo=Object(Te.withStyles)(function(e){var t;return{textBlock:(t={margin:5*e.spacing.unit,marginRight:10*e.spacing.unit,marginLeft:10*e.spacing.unit},Object(I.a)(t,e.breakpoints.down("sm"),{margin:1*e.spacing.unit,marginRight:2*e.spacing.unit,marginLeft:2*e.spacing.unit}),Object(I.a)(t,e.breakpoints.up("lg"),{margin:5*e.spacing.unit,marginRight:20*e.spacing.unit,marginLeft:20*e.spacing.unit}),t),title:{marginTop:4*e.spacing.unit,marginBottom:e.spacing.unit}}})(function(e){var t=e.classes;return r.a.createElement("div",null,r.a.createElement("div",{className:t.textBlock},r.a.createElement(ot.a,{className:t.title,variant:"h3"},"Terms and Conditions"),r.a.createElement(ot.a,{variant:"subtitle1"},'These terms and conditions ("Terms", "Agreement") are an agreement between Website Operator ("Website Operator", "us", "we" or "our") and you ("User", "you" or "your"). This Agreement sets forth the general terms and conditions of your use of the ',r.a.createElement("a",{target:"_blank",rel:"noopener noreferrer",href:"http://www.scigym.ai"},"scigym.ai"),' website and any of its products or services (collectively, "Website" or "Services").'),r.a.createElement(ot.a,{className:t.title,variant:"h4"},"Accounts and membership"),r.a.createElement(ot.a,{variant:"subtitle1"},"If you create an account on the Website, you are responsible for maintaining the security of your account and you are fully responsible for all activities that occur under the account and any other actions taken in connection with it. We may, but have no obligation to, monitor and review new accounts before you may sign in and use our Services. Providing false contact information of any kind may result in the termination of your account. You must immediately notify us of any unauthorized uses of your account or any other breaches of security. We will not be liable for any acts or omissions by you, including any damages of any kind incurred as a result of such acts or omissions. We may suspend, disable, or delete your account (or any part thereof) if we determine that you have violated any provision of this Agreement or that your conduct or content would tend to damage our reputation and goodwill. If we delete your account for the foregoing reasons, you may not re-register for our Services. We may block your email address and Internet protocol address to prevent further registration."),r.a.createElement(ot.a,{className:t.title,variant:"h4"},"User content"),r.a.createElement(ot.a,{variant:"subtitle1"},'We do not own any data, information or material ("Content") that you submit on the Website in the course of using the Service. You shall have sole responsibility for the accuracy, quality, integrity, legality, reliability, appropriateness, and intellectual property ownership or right to use of all submitted Content. We may, but have no obligation to, monitor and review Content on the Website submitted or created using our Services by you. Unless specifically permitted by you, your use of the Website does not grant us the license to use, reproduce, adapt, modify, publish or distribute the Content created by you or stored in your user account for commercial, marketing or any similar purpose. But you grant us permission to access, copy, distribute, store, transmit, reformat, display and perform the Content of your user account solely as required for the purpose of providing the Services to you. Without limiting any of those representations or warranties, we have the right, though not the obligation, to, in our own sole discretion, refuse or remove any Content that, in our reasonable opinion, violates any of our policies or is in any way harmful or objectionable.'),r.a.createElement(ot.a,{className:t.title,variant:"h4"},"Backups"),r.a.createElement(ot.a,{variant:"subtitle1"},"We perform regular backups of the Website and Content, however, these backups are for our own administrative purposes only and are in no way guaranteed. You are responsible for maintaining your own backups of your data. We do not provide any sort of compensation for lost or incomplete data in the event that backups do not function properly. We will do our best to ensure complete and accurate backups, but assume no responsibility for this duty."),r.a.createElement(ot.a,{className:t.title,variant:"h4"},"Links to other websites"),r.a.createElement(ot.a,{variant:"subtitle1"},"Although this Website may link to other websites, we are not, directly or indirectly, implying any approval, association, sponsorship, endorsement, or affiliation with any linked website, unless specifically stated herein. We are not responsible for examining or evaluating, and we do not warrant the offerings of, any businesses or individuals or the content of their websites. We do not assume any responsibility or liability for the actions, products, services, and content of any other third-parties. You should carefully review the legal statements and other conditions of use of any website which you access through a link from this Website. Your linking to any other off-site websites is at your own risk."),r.a.createElement(ot.a,{className:t.title,variant:"h4"},"Prohibited uses"),r.a.createElement(ot.a,{variant:"subtitle1"},"In addition to other terms as set forth in the Agreement, you are prohibited from using the Website or its Content: (a) for any unlawful purpose; (b) to solicit others to perform or participate in any unlawful acts; (c) to violate any international, federal, provincial or state regulations, rules, laws, or local ordinances; (d) to infringe upon or violate our intellectual property rights or the intellectual property rights of others; (e) to harass, abuse, insult, harm, defame, slander, disparage, intimidate, or discriminate based on gender, sexual orientation, religion, ethnicity, race, age, national origin, or disability; (f) to submit false or misleading information; (g) to upload or transmit viruses or any other type of malicious code that will or may be used in any way that will affect the functionality or operation of the Service or of any related website, other websites, or the Internet; (h) to collect or track the personal information of others; (i) to spam, phish, pharm, pretext, spider, crawl, or scrape; (j) for any obscene or immoral purpose; or (k) to interfere with or circumvent the security features of the Service or any related website, other websites, or the Internet. We reserve the right to terminate your use of the Service or any related website for violating any of the prohibited uses."),r.a.createElement(ot.a,{className:t.title,variant:"h4"},"Limitation of liability"),r.a.createElement(ot.a,{variant:"subtitle1"},"To the fullest extent permitted by applicable law, in no event will Website Operator, its affiliates, officers, directors, employees, agents, suppliers or licensors be liable to any person for (a): any indirect, incidental, special, punitive, cover or consequential damages (including, without limitation, damages for lost profits, revenue, sales, goodwill, use of content, impact on business, business interruption, loss of anticipated savings, loss of business opportunity) however caused, under any theory of liability, including, without limitation, contract, tort, warranty, breach of statutory duty, negligence or otherwise, even if Website Operator has been advised as to the possibility of such damages or could have foreseen such damages. To the maximum extent permitted by applicable law, the aggregate liability of Website Operator and its affiliates, officers, employees, agents, suppliers and licensors, relating to the services will be limited to an amount greater of one dollar or any amounts actually paid in cash by you to Website Operator for the prior one month period prior to the first event or occurrence giving rise to such liability. The limitations and exclusions also apply if this remedy does not fully compensate you for any losses or fails of its essential purpose."),r.a.createElement(ot.a,{className:t.title,variant:"h4"},"Indemnification"),r.a.createElement(ot.a,{variant:"subtitle1"},"You agree to indemnify and hold Website Operator and its affiliates, directors, officers, employees, and agents harmless from and against any liabilities, losses, damages or costs, including reasonable attorneys' fees, incurred in connection with or arising from any third-party allegations, claims, actions, disputes, or demands asserted against any of them as a result of or relating to your Content, your use of the Website or Services or any willful misconduct on your part."),r.a.createElement(ot.a,{className:t.title,variant:"h4"},"Severability"),r.a.createElement(ot.a,{variant:"subtitle1"},"All rights and restrictions contained in this Agreement may be exercised and shall be applicable and binding only to the extent that they do not violate any applicable laws and are intended to be limited to the extent necessary so that they will not render this Agreement illegal, invalid or unenforceable. If any provision or portion of any provision of this Agreement shall be held to be illegal, invalid or unenforceable by a court of competent jurisdiction, it is the intention of the parties that the remaining provisions or portions thereof shall constitute their agreement with respect to the subject matter hereof, and all such remaining provisions or portions thereof shall remain in full force and effect."),r.a.createElement(ot.a,{className:t.title,variant:"h4"},"Changes and amendments"),r.a.createElement(ot.a,{variant:"subtitle1"},"We reserve the right to modify this Agreement or its policies relating to the Website or Services at any time, effective upon posting of an updated version of this Agreement on the Website. When we do, we will revise the updated date at the bottom of this page. Continued use of the Website after any such changes shall constitute your consent to such changes. Policy was created with ",r.a.createElement("a",{target:"_blank",rel:"noopener noreferrer",title:"Generate terms and conditions",href:"https://www.websitepolicies.com/terms-and-conditions-generator"},"WebsitePolicies"),"."),r.a.createElement(ot.a,{className:t.title,variant:"h4"},"Acceptance of these terms"),r.a.createElement(ot.a,{variant:"subtitle1"},"You acknowledge that you have read this Agreement and agree to all its terms and conditions. By using the Website or its Services you agree to be bound by this Agreement. If you do not agree to abide by the terms of this Agreement, you are not authorized to use or access the Website and its Services."),r.a.createElement(ot.a,{className:t.title,variant:"h4"},"Contacting us"),r.a.createElement(ot.a,{variant:"subtitle1"},"If you would like to contact us to understand more about this Agreement or wish to contact us concerning any matter relating to it, you may send an email to info@scigym.ai"),r.a.createElement(ot.a,{variant:"subtitle1"},"This document was last updated on August 30, 2019")))}),_o=a(211),To=a.n(_o),Co=function(e){return function(t){t({type:d.GET_COMMENTS_LIST}),ne.getComments(e).then(function(e){t({type:d.GET_COMMENTS_LIST_SUCCESS,payload:e.data})}).catch(function(e){t({type:d.GET_COMMENTS_LIST_FAILURE}),re(e)})}},Ro=function(){return function(e){e({type:d.RESET_COMMENTS_PROPS})}},Io=Object(Te.withStyles)(function(e){return{root:{width:"100%"},child:{width:"auto",margin:2*e.spacing.unit},textField:{width:"100%"}}})(function(e){var t=e.classes,a=e.commentText,n=e.handleChange,o=e.errors;return r.a.createElement("div",{className:t.root},r.a.createElement("div",{className:t.child},r.a.createElement(Ja.a,{id:"filled-full-description",label:"Leave a comment",className:t.textField,value:Boolean(a)?a:"",onChange:n("commentText"),multiline:!0,rows:10,margin:"normal",variant:"filled",error:Boolean(o&&o.description),helperText:o?o.description:"We support styling with markdown."})))}),No=function(e){function t(e){var a;Object(Y.a)(this,t),(a=Object(K.a)(this,Object(Z.a)(t).call(this,e))).handleSubmit=function(e){e.preventDefault();var t=Boolean(a.props.comment),n=a.state,r=n.commentText,o=n.messageboardId;if(a.props.quote)a.props.createComment(r,o);else if(t){var i=a.props.comment.id;a.props.editComment(i,r,o)}else{var s=a.state,c=s.commentText,l=s.messageboardId;a.props.createComment(c,l)}},a.handleChange=function(e){return function(t){t.preventDefault(),a.setState(Object(I.a)({},e,t.target.value))}};var n=e.comment,r=e.messageboard,o=Boolean(n);return a.state={commentId:o?n.id:void 0,messageboardId:Boolean(r)?r.id:void 0,commentText:o?n.comment:""},a.handleSubmit=a.handleSubmit.bind(Object(ut.a)(Object(ut.a)(a))),a.handleChange=a.handleChange.bind(Object(ut.a)(Object(ut.a)(a))),a}return Object(J.a)(t,e),Object(q.a)(t,[{key:"componentDidUpdate",value:function(e){if(e.uploadSuccess!==this.props.uploadSuccess&&this.props.uploadSuccess&&(this.props.onClose(),this.props.resetCommentsProps()),void 0===this.state.messageboardId&&this.props.messageboard&&this.setState({messageboardId:this.props.messageboard.id}),void 0===this.state.commentId&&this.props.comment&&this.setState({commentId:this.props.comment.id}),this.props.quote&&!e.quote){for(var t=this.props.comment,a=t.created.split("T")[0],n=">*posted by "+t.author.username+" on "+a+"*\n\n"+">"+t.comment.replace(/\n/g,"\n>")+" \n\n";n.includes("\n>\n");)n=n.replace(/\n>\n/g,"\n\n");this.setState({commentText:n})}!this.props.quote&&e.quote&&this.setState({commentText:this.props.comment.comment})}},{key:"render",value:function(){var e=this.props,t=e.classes,a=e.errors,n=e.loading;return r.a.createElement("form",{className:t.container},r.a.createElement(Xe.a,{onClose:this.props.onClose,open:this.props.open,fullWidth:!0},r.a.createElement(Ze.a,null,this.title),r.a.createElement(Io,{commentText:this.state.commentText,handleChange:this.handleChange,errors:a}),n?r.a.createElement(te.a,{className:t.loadingStyle,size:30,disableShrink:!0,color:"primary"}):r.a.createElement(Me.a,{onClick:this.handleSubmit},"Submit")))}},{key:"title",get:function(){if(this.props.quote)return"Reply to a Comment";if(Boolean(this.props.comment)){return"Edit your Comment"}return"Make a Comment"}}]),t}(n.Component);var Ao={createComment:function(){for(var e=arguments.length,t=new Array(e),a=0;a next => action => {\n // coming from the async action hitting the user endpoint\n if (\n action.type === types.LOGIN_USER_GITHUB_OAUTH_SUCCESS ||\n action.type === types.REFRESH_AUTH_TOKEN_SUCCESS\n ) {\n const { accessToken, refreshToken } = action.payload;\n setAuthorizationHeader(accessToken, refreshToken);\n }\n if (action.type === types.LOGOUT_USER_SUCCESS || action.type === types.DELETE_USER_SUCCESS) {\n clearAuthorizationHeader();\n }\n next(action);\n};\n\nexport default AuthMiddleware;\n","import uuidv4 from 'uuid/v4';\n\nimport types from '../utils/types';\n\nconst initialState = {\n loaded: false,\n githubRandomState: uuidv4(),\n githubClientId: undefined,\n githubCallbackUrl: undefined,\n};\n\nexport default (state = initialState, action) => {\n switch (action.type) {\n case types.GET_API_CONFIG_SUCCESS: {\n return {\n ...state,\n ...action.payload,\n loaded: true,\n };\n }\n default:\n return state;\n }\n};\n","import types from '../utils/types';\n\nconst initialState = {\n accessToken: undefined,\n expiresIn: undefined,\n firstName: undefined,\n id: undefined,\n lastName: undefined,\n refreshToken: undefined,\n scope: undefined,\n tokenType: undefined,\n username: undefined,\n exists: false,\n};\n\nexport default (state = initialState, action) => {\n switch (action.type) {\n case types.LOGIN_USER_GITHUB_OAUTH_SUCCESS:\n case types.REFRESH_AUTH_TOKEN_SUCCESS:\n case types.GET_USER_PROFILE_SUCCESS: {\n return { ...state, ...action.payload, exists: true };\n }\n case types.UPDATE_USER_PROFILE_SUCCESS: {\n return { ...state, ...action.payload };\n }\n case types.DELETE_USER_SUCCESS:\n case types.LOGOUT_USER_SUCCESS: {\n return { ...initialState, exists: false };\n }\n default:\n return state;\n }\n};\n","import uuidv4 from 'uuid/v4';\nimport types from '../utils/types';\n\nconst initialState = {\n notifications: [],\n loaders: {},\n};\n\nconst notifications = {\n [types.LOGIN_USER_GITHUB_OAUTH_SUCCESS]: 'Successfully logged in.',\n [types.LOGIN_USER_GITHUB_OAUTH_FAILURE]: 'Failed to login through github. Please try again later',\n [types.UPDATE_USER_PROFILE_SUCCESS]: 'Successfully updated your profile.',\n [types.UPDATE_USER_PROFILE_FAILURE]: 'Failed to update your profile. Please try again later',\n [types.FIND_GYM_REPOS]: 'We are updating your repositories.',\n [types.FIND_GYM_REPOS_SUCCESS]: 'Successfully updated your repositories',\n [types.FIND_GYM_REPOS_FAILURE]: 'Failed to update your repositories. Please try again later',\n [types.LOGOUT_USER_SUCCESS]: 'Successfully logged out.',\n [types.LOGOUT_USER_FAILURE]: 'Failed to log out. Please try again later.',\n [types.CREATE_ENVIRONMENT_SUCCESS]: 'Successfully uploaded environment.',\n [types.CREATE_ENVIRONMENT_FAILURE]: 'Failed to upload environment. Please try again later.',\n [types.EDIT_ENVIRONMENT_SUCCESS]: 'Successfully updated environment.',\n [types.EDIT_ENVIRONMENT_FAILURE]: 'Failed to update environment. Please try again later.',\n [types.DELETE_ENVIRONMENT_SUCCESS]: 'Successfully deleted environment.',\n [types.DELETE_ENVIRONMENT_FAILURE]: 'Failed to delete environment. Please try again later.',\n [types.CREATE_IMAGE_SUCCESS]: 'Successfully uploaded image.',\n [types.CREATE_IMAGE_FAILURE]: 'Failed to upload image. Please try again later.',\n [types.DELETE_IMAGE_SUCCESS]: 'Successfully deleted image.',\n [types.DELETE_IMAGE_FAILURE]: 'Failed to delete image. Please try again later.',\n [types.CREATE_COMMENT_SUCCESS]: 'Successfully posted a reply.',\n [types.CREATE_COMMENT_FAILURE]: 'Failed to post a reply. Please try again later.',\n [types.EDIT_COMMENT_SUCCESS]: 'Successfully updated your reply.',\n [types.EDIT_COMMENT_FAILURE]: 'Failed to update your reply. Please try again later.',\n [types.DELETE_COMMENT_SUCCESS]: 'Successfully deleted your reply.',\n [types.DELETE_COMMENT_FAILURE]: 'Failed to delete your reply. Please try again later.',\n [types.CREATE_MESSAGEBOARD_SUCCESS]: 'Successfully opened a new discussion.',\n [types.CREATE_MESSAGEBOARD_FAILURE]: 'Failed to open a new discussion. Please try again later.',\n [types.EDIT_MESSAGEBOARD_SUCCESS]: 'Successfully updated your discussion.',\n [types.EDIT_MESSAGEBOARD_FAILURE]: 'Failed to update your discussion. Please try again later.',\n [types.DELETE_MESSAGEBOARD_SUCCESS]: 'Successfully deleted your message board.',\n [types.DELETE_MESSAGEBOARD_FAILURE]: 'Failed to delete your message board. Please try again later.',\n};\n\nfunction getNotifications(state, actionType) {\n const message = notifications[actionType];\n const notification = { key: uuidv4(), message };\n return message ? state.notifications.concat([notification]) : state.notifications;\n}\n\nconst setLoading = (state, key, actionType) => ({\n ...state,\n loaders: {\n ...state.loaders,\n [key]: true,\n },\n notifications: getNotifications(state, actionType),\n});\n\nconst removeLoading = (state, key, actionType) => ({\n loaders: {\n ...state.loaders,\n [key]: false,\n },\n notifications: getNotifications(state, actionType),\n});\n\nexport default (state = initialState, action) => {\n switch (action.type) {\n case types.LOGIN_USER_GITHUB_OAUTH:\n case types.REFRESH_AUTH_TOKEN:\n case types.GET_USER_PROFILE: {\n return setLoading(state, types.LOGIN_USER_GITHUB_OAUTH, action.type);\n }\n\n case types.LOGIN_USER_GITHUB_OAUTH_SUCCESS:\n case types.LOGIN_USER_GITHUB_OAUTH_FAILURE:\n case types.REFRESH_AUTH_TOKEN_SUCCESS:\n case types.REFRESH_AUTH_TOKEN_FAILURE:\n case types.GET_USER_PROFILE_SUCCESS:\n case types.GET_USER_PROFILE_FAILURE: {\n return removeLoading(state, types.LOGIN_USER_GITHUB_OAUTH, action.type);\n }\n case types.LOGOUT_USER:\n case types.LOGOUT_USER_SUCCESS:\n case types.LOGOUT_USER_FAILURE:\n case types.UPDATE_USER_PROFILE: {\n return setLoading(state, types.UPDATE_USER_PROFILE, action.type);\n }\n case types.UPDATE_USER_PROFILE_SUCCESS: {\n return removeLoading(state, types.UPDATE_USER_PROFILE, action.type);\n }\n case types.UPDATE_USER_PROFILE_FAILURE: {\n return removeLoading(state, types.UPDATE_USER_PROFILE, action.type);\n }\n\n case types.FIND_GYM_REPOS: {\n return setLoading(state, types.FIND_GYM_REPOS, action.type);\n }\n case types.FIND_GYM_REPOS_SUCCESS: {\n return removeLoading(state, types.FIND_GYM_REPOS, action.type);\n }\n case types.FIND_GYM_REPOS_FAILURE: {\n return removeLoading(state, types.FIND_GYM_REPOS, action.type);\n }\n case types.CREATE_ENVIRONMENT: {\n return setLoading(state, types.CREATE_ENVIRONMENT, action.type);\n }\n case types.CREATE_ENVIRONMENT_SUCCESS: {\n return removeLoading(state, types.CREATE_ENVIRONMENT, action.type);\n }\n case types.CREATE_ENVIRONMENT_FAILURE: {\n return removeLoading(state, types.CREATE_ENVIRONMENT, action.type);\n }\n case types.EDIT_ENVIRONMENT: {\n return setLoading(state, types.EDIT_ENVIRONMENT, action.type);\n }\n case types.EDIT_ENVIRONMENT_SUCCESS: {\n return removeLoading(state, types.EDIT_ENVIRONMENT, action.type);\n }\n case types.EDIT_ENVIRONMENT_FAILURE: {\n return removeLoading(state, types.EDIT_ENVIRONMENT, action.type);\n }\n case types.DELETE_ENVIRONMENT: {\n return setLoading(state, types.DELETE_ENVIRONMENT, action.type);\n }\n case types.DELETE_ENVIRONMENT_SUCCESS: {\n return removeLoading(state, types.DELETE_ENVIRONMENT, action.type);\n }\n case types.DELETE_ENVIRONMENT_FAILURE: {\n return removeLoading(state, types.DELETE_ENVIRONMENT, action.type);\n }\n case types.CREATE_IMAGE: {\n return setLoading(state, types.CREATE_IMAGE, action.type);\n }\n case types.CREATE_IMAGE_SUCCESS: {\n return removeLoading(state, types.CREATE_IMAGE, action.type);\n }\n case types.CREATE_IMAGE_FAILURE: {\n return removeLoading(state, types.CREATE_IMAGE, action.type);\n }\n case types.DELETE_IMAGE: {\n return setLoading(state, types.DELETE_IMAGE, action.type);\n }\n case types.DELETE_IMAGE_SUCCESS: {\n return removeLoading(state, types.DELETE_IMAGE, action.type);\n }\n case types.DELETE_IMAGE_FAILURE: {\n return removeLoading(state, types.DELETE_IMAGE, action.type);\n }\n case types.CREATE_MESSAGEBOARD: {\n return setLoading(state, types.CREATE_MESSAGEBOARD, action.type);\n }\n case types.CREATE_MESSAGEBOARD_SUCCESS: {\n return removeLoading(state, types.CREATE_MESSAGEBOARD, action.type);\n }\n case types.CREATE_MESSAGEBOARD_FAILURE: {\n return removeLoading(state, types.CREATE_MESSAGEBOARD, action.type);\n }\n case types.EDIT_MESSAGEBOARD: {\n return setLoading(state, types.EDIT_MESSAGEBOARD, action.type);\n }\n case types.EDIT_MESSAGEBOARD_SUCCESS: {\n return removeLoading(state, types.EDIT_MESSAGEBOARD, action.type);\n }\n case types.EDIT_MESSAGEBOARD_FAILURE: {\n return removeLoading(state, types.EDIT_MESSAGEBOARD, action.type);\n }\n case types.DELETE_MESSAGEBOARD: {\n return setLoading(state, types.DELETE_MESSAGEBOARD, action.type);\n }\n case types.DELETE_MESSAGEBOARD_SUCCESS: {\n return removeLoading(state, types.DELETE_MESSAGEBOARD, action.type);\n }\n case types.DELETE_MESSAGEBOARD_FAILURE: {\n return removeLoading(state, types.DELETE_MESSAGEBOARD, action.type);\n }\n case types.GET_COMMENTS_LIST: {\n return setLoading(state, types.GET_COMMENTS_LIST, action.type);\n }\n case types.GET_COMMENTS_LIST_SUCCESS: {\n return removeLoading(state, types.GET_COMMENTS_LIST, action.type);\n }\n case types.GET_COMMENTS_LIST_FAILURE: {\n return removeLoading(state, types.GET_COMMENTS_LIST, action.type);\n }\n case types.CREATE_COMMENT: {\n return setLoading(state, types.CREATE_COMMENT, action.type);\n }\n case types.CREATE_COMMENT_SUCCESS: {\n return removeLoading(state, types.CREATE_COMMENT, action.type);\n }\n case types.CREATE_COMMENT_FAILURE: {\n return removeLoading(state, types.CREATE_COMMENT, action.type);\n }\n case types.EDIT_COMMENT: {\n return setLoading(state, types.EDIT_COMMENT, action.type);\n }\n case types.EDIT_COMMENT_SUCCESS: {\n return removeLoading(state, types.EDIT_COMMENT, action.type);\n }\n case types.EDIT_COMMENT_FAILURE: {\n return removeLoading(state, types.EDIT_COMMENT, action.type);\n }\n case types.DELETE_COMMENT: {\n return setLoading(state, types.DELETE_COMMENT, action.type);\n }\n case types.DELETE_COMMENT_SUCCESS: {\n return removeLoading(state, types.DELETE_COMMENT, action.type);\n }\n case types.DELETE_COMMENT_FAILURE: {\n return removeLoading(state, types.DELETE_COMMENT, action.type);\n }\n default:\n return state;\n }\n};\n\n/** selectors */\nexport function isLoading(state, key) {\n return state.loaders[key] === true;\n}\n","import types from '../utils/types';\n\nconst initialState = {\n environments: [],\n searchedEnvironments: undefined,\n categorizedEnvironments: undefined,\n searchedTopic: undefined,\n uploadSuccess: undefined,\n deleteSuccess: undefined,\n};\n\nexport default (state = initialState, action) => {\n switch (action.type) {\n case types.GET_ENVIRONMENTS_LIST_SUCCESS: {\n return {\n ...state,\n environments: action.payload.results,\n };\n }\n // case types.DELETE_ENVIRONMENT: // environments: state.environments.filter(env => env.id !== envId), causes weird rerender\n case types.DELETE_ENVIRONMENT_SUCCESS: {\n return {\n ...state,\n deleteSuccess: true,\n };\n }\n case types.DELETE_ENVIRONMENT_FAILURE: {\n return {\n ...state,\n deleteSuccess: false,\n };\n }\n case types.CREATE_ENVIRONMENT_SUCCESS: {\n return {\n ...state,\n uploadSuccess: true,\n };\n }\n case types.CREATE_ENVIRONMENT_FAILURE: {\n return {\n ...state,\n uploadSuccess: false,\n };\n }\n case types.EDIT_ENVIRONMENT_SUCCESS: {\n return {\n ...state,\n uploadSuccess: true,\n };\n }\n case types.EDIT_ENVIRONMENT_FAILURE: {\n return {\n ...state,\n uploadSuccess: false,\n };\n }\n case types.RESET_ENVIRONMENTS_PROPS: {\n return {\n ...state,\n uploadSuccess: undefined,\n deleteSuccess: undefined,\n };\n }\n case types.SEARCH_ENVIRONMENTS_SUCCESS: {\n return {\n ...state,\n searchedEnvironments: action.payload,\n };\n }\n case types.SEARCH_ENVIRONMENTS_FAILURE: {\n return {\n ...state,\n searchedEnvironments: [],\n };\n }\n case types.SEARCH_ENVIRONMENTS_RESET: {\n return {\n ...state,\n searchedEnvironments: undefined,\n };\n }\n case types.CATEGORIZE_ENVIRONMENTS_SUCCESS: {\n return {\n ...state,\n categorizedEnvironments: action.environment,\n searchedTopic: action.topic,\n };\n }\n case types.CATEGORIZE_ENVIRONMENTS_FAILURE: {\n return {\n ...state,\n categorizedEnvironments: [],\n };\n }\n case types.CATEGORIZE_ENVIRONMENTS_RESET: {\n return {\n ...state,\n categorizedEnvironments: undefined,\n searchedTopic: undefined,\n };\n }\n default:\n return state;\n }\n};\n","import types from '../utils/types';\n\nconst initialState = {};\n\nfunction _parseApiError(error) {\n const { data } = error.response;\n return Object.keys(data).reduce((errors, key) => {\n return { ...errors, [key]: data[key][0] };\n }, {});\n}\n\nexport default (state = initialState, action) => {\n switch (action.type) {\n case types.UPDATE_USER_PROFILE:\n case types.UPDATE_USER_PROFILE_SUCCESS: {\n return { ...state, [types.UPDATE_USER_PROFILE]: null };\n }\n case types.UPDATE_USER_PROFILE_FAILURE: {\n return { ...state, [types.UPDATE_USER_PROFILE]: _parseApiError(action.payload) };\n }\n case types.CREATE_ENVIRONMENT:\n case types.CREATE_ENVIRONMENT_SUCCESS: {\n return { ...state, [types.CREATE_ENVIRONMENT]: null };\n }\n case types.CREATE_ENVIRONMENT_FAILURE: {\n return { ...state, [types.CREATE_ENVIRONMENT]: _parseApiError(action.payload) };\n }\n case types.EDIT_ENVIRONMENT:\n case types.EDIT_ENVIRONMENT_SUCCESS: {\n return { ...state, [types.EDIT_ENVIRONMENT]: null };\n }\n case types.EDIT_ENVIRONMENT_FAILURE: {\n return { ...state, [types.EDIT_ENVIRONMENT]: _parseApiError(action.payload) };\n }\n case types.RESET_ENVIRONMENTS_ERRORS: {\n return {\n ...state,\n [types.EDIT_ENVIRONMENT]: null,\n [types.CREATE_ENVIRONMENT]: null,\n };\n }\n case types.CREATE_MESSAGEBOARD:\n case types.CREATE_MESSAGEBOARD_SUCCESS: {\n return { ...state, [types.CREATE_MESSAGEBOARD]: null };\n }\n case types.CREATE_MESSAGEBOARD_FAILURE: {\n return { ...state, [types.CREATE_MESSAGEBOARD]: _parseApiError(action.payload) };\n }\n case types.EDIT_MESSAGEBOARD:\n case types.EDIT_MESSAGEBOARD_SUCCESS: {\n return { ...state, [types.EDIT_MESSAGEBOARD]: null };\n }\n case types.EDIT_MESSAGEBOARD_FAILURE: {\n return { ...state, [types.EDIT_MESSAGEBOARD]: _parseApiError(action.payload) };\n }\n case types.RESET_MESSAGEBOARDS_ERRORS: {\n return {\n ...state,\n [types.EDIT_MESSAGEBOARD]: null,\n [types.CREATE_MESSAGEBOARD]: null,\n };\n }\n case types.CREATE_COMMENT:\n case types.CREATE_COMMENT_SUCCESS: {\n return { ...state, [types.CREATE_COMMENT]: null };\n }\n case types.CREATE_COMMENT_FAILURE: {\n return { ...state, [types.CREATE_COMMENT]: _parseApiError(action.payload) };\n }\n case types.EDIT_COMMENT:\n case types.EDIT_COMMENT_SUCCESS: {\n return { ...state, [types.EDIT_COMMENT]: null };\n }\n case types.EDIT_COMMENT_FAILURE: {\n return { ...state, [types.EDIT_COMMENT]: _parseApiError(action.payload) };\n }\n case types.RESET_COMMENTS_ERRORS: {\n return {\n ...state,\n [types.EDIT_COMMENT]: null,\n [types.CREATE_COMMENT]: null,\n };\n }\n default:\n return state;\n }\n};\n\n/** selectors */\nexport function getErrors(state, key) {\n const error = state[key];\n if (error) {\n return error;\n }\n return false;\n}\n","import types from '../utils/types';\n\nconst initialState = {\n userRepositories: [],\n repositories: [],\n};\n\nexport default (state = initialState, action) => {\n switch (action.type) {\n case types.GET_USER_REPOSITORIES_LIST_SUCCESS: {\n return { ...state, userRepositories: action.payload };\n }\n case types.GET_REPOSITORIES_SUCCESS: {\n return { ...state, repositories: action.payload.results };\n }\n default:\n return state;\n }\n};\n","import types from '../utils/types';\n\nconst initialState = {\n topics: [],\n};\n\nexport default (state = initialState, action) => {\n switch (action.type) {\n case types.GET_TOPICS_SUCCESS: {\n return {\n ...state,\n topics: action.payload.results,\n };\n }\n default:\n return state;\n }\n};\n","import types from '../utils/types';\n\nconst initialState = {\n userImages: [],\n imageConfig: [],\n deleteSuccess: undefined,\n uploadedImage: undefined,\n};\n\nexport default (state = initialState, action) => {\n switch (action.type) {\n case types.CREATE_IMAGE_SUCCESS: {\n return { ...state, uploadedImage: action.payload };\n }\n case types.CREATE_IMAGE_FAILURE: {\n return { ...state, uploadedImage: false };\n }\n case types.DELETE_IMAGE_SUCCESS: {\n return {\n ...state,\n deleteSuccess: true,\n };\n }\n case types.DELETE_IMAGE_FAILURE: {\n return { ...state, deleteSuccess: false };\n }\n case types.GET_USER_IMAGES_LIST_SUCCESS: {\n return { ...state, userImages: action.payload };\n }\n // case types.DELETE_IMAGE: { // weird rerender\n // const imageId = action.meta.id;\n // return {\n // ...state,\n // userImages: state.userImages.filter(image => image.id !== imageId),\n // };\n // }\n case types.GET_IMAGE_CONFIG_LIST_SUCCESS: {\n return { ...state, imageConfig: action.payload };\n }\n case types.RESET_IMAGE_PROPS: {\n return { ...state, deleteSuccess: undefined, uploadedImage: undefined };\n }\n default:\n return state;\n }\n};\n","import types from '../utils/types';\n\nconst initialState = {\n contributors: [],\n};\n\nexport default (state = initialState, action) => {\n switch (action.type) {\n case types.GET_CONTRIBUTORS_LIST_SUCCESS: {\n return {\n ...state,\n contributors: action.payload.results,\n };\n }\n default:\n return state;\n }\n};\n","import types from '../utils/types';\n\nconst initialState = {\n projectauthors: [],\n};\n\nexport default (state = initialState, action) => {\n switch (action.type) {\n case types.GET_PROJECTAUTHORS_LIST_SUCCESS: {\n return {\n ...state,\n projectauthors: action.payload.results,\n };\n }\n default:\n return state;\n }\n};\n","import types from '../utils/types';\n\nconst initialState = {\n messageboards: [],\n num_comments: {},\n uploadSuccess: undefined,\n deleteSuccess: undefined,\n};\n\nexport default (state = initialState, action) => {\n switch (action.type) {\n case types.GET_MESSAGEBOARDS_LIST_SUCCESS: {\n return {\n ...state,\n messageboards: action.payload.results,\n };\n }\n case types.DELETE_MESSAGEBOARD_SUCCESS: {\n return {\n ...state,\n deleteSuccess: true,\n };\n }\n case types.DELETE_MESSAGEBOARD_FAILURE: {\n return {\n ...state,\n deleteSuccess: false,\n };\n }\n case types.CREATE_MESSAGEBOARD_SUCCESS: {\n return {\n ...state,\n uploadSuccess: true,\n };\n }\n case types.CREATE_MESSAGEBOARD_FAILURE: {\n return {\n ...state,\n uploadSuccess: false,\n };\n }\n case types.EDIT_MESSAGEBOARD_SUCCESS: {\n return {\n ...state,\n uploadSuccess: true,\n };\n }\n case types.EDIT_MESSAGEBOARD_FAILURE: {\n return {\n ...state,\n uploadSuccess: false,\n };\n }\n case types.RESET_MESSAGEBOARDS_PROPS: {\n return {\n ...state,\n uploadSuccess: undefined,\n deleteSuccess: undefined,\n };\n }\n case types.COUNT_COMMENTS_SUCCESS: {\n return {\n ...state,\n num_comments: action.payload\n }\n }\n default:\n return state;\n }\n};\n","import types from '../utils/types';\n\nconst initialState = {\n comments: [],\n uploadSuccess: undefined,\n deleteSuccess: undefined,\n};\n\nexport default (state = initialState, action) => {\n switch (action.type) {\n case types.GET_COMMENTS_LIST_SUCCESS: {\n return {\n ...state,\n comments: action.payload,\n };\n }\n case types.DELETE_COMMENT_SUCCESS: {\n return {\n ...state,\n deleteSuccess: true,\n };\n }\n case types.DELETE_COMMENT_FAILURE: {\n return {\n ...state,\n deleteSuccess: false,\n };\n }\n case types.CREATE_COMMENT_SUCCESS: {\n return {\n ...state,\n uploadSuccess: true,\n };\n }\n case types.CREATE_COMMENT_FAILURE: {\n return {\n ...state,\n uploadSuccess: false,\n };\n }\n case types.EDIT_COMMENT_SUCCESS: {\n return {\n ...state,\n uploadSuccess: true,\n };\n }\n case types.EDIT_COMMENT_FAILURE: {\n return {\n ...state,\n uploadSuccess: false,\n };\n }\n case types.RESET_COMMENTS_PROPS: {\n return {\n ...state,\n uploadSuccess: undefined,\n deleteSuccess: undefined,\n };\n }\n case types.COUNT_COMMENTS_SUCCESS: {\n return {\n ...state,\n num_comments: action.payload\n }\n }\n case types.RESET_COMMENTS: {\n return {\n ...state,\n comments: [],\n }\n }\n default:\n return state;\n }\n};","import { combineReducers } from 'redux';\n\nimport config from './config';\nimport user from './user';\nimport display from './display';\nimport environments from './environments';\nimport errors from './errors';\nimport repositories from './repositories';\nimport topics from './topics';\nimport images from './images';\nimport contributors from './contributors';\nimport projectauthors from './projectauthors';\nimport messageboards from './messageboards';\nimport comments from './comments';\n\nconst rootReducer = combineReducers({\n config,\n display,\n user,\n environments,\n errors,\n repositories,\n contributors,\n projectauthors,\n topics,\n images,\n messageboards,\n comments,\n});\n\nexport default rootReducer;\n","import axios from 'axios';\n\nimport { getHost } from './environment';\n\nclass ScigymApiClient {\n constructor(base, version) {\n this.base = base;\n this.version = version;\n this.url = `${base}/${version}`;\n }\n\n config() {\n return axios.get(`${this.url}/app_config/`);\n }\n\n login(code) {\n return axios.post(`${this.url}/users/${code}/github_oauth/`);\n }\n\n logout(clientId) {\n return axios.post(`${this.url}/users/logout/`);\n }\n\n refreshToken(token) {\n return axios.post(`${this.url}/users/${token}/refresh_token/`);\n }\n\n me() {\n return axios.get(`${this.url}/users/me/`);\n }\n\n updateMe(form) {\n return axios.post(`${this.url}/users/update_me/`, form);\n }\n\n deleteMe() {\n return axios.post(`${this.url}/users/delete_me/`);\n }\n\n status() {\n return axios.get(`${this.base}/watchman/`);\n }\n\n environments(params) {\n return axios.get(`${this.url}/environments/`);\n }\n\n createEnvironment(name, description, repositoryId, tags, topicUUID, avatarUUID) {\n return axios.post(`${this.url}/environments/`, {\n name: name,\n description: description,\n repository: repositoryId,\n tags: tags,\n topic: topicUUID,\n avatar: avatarUUID,\n });\n }\n\n editEnvironment(environment) {\n return axios.put(`${this.url}/environments/${environment.id}/`, environment);\n }\n\n deleteEnvironment(environment) {\n return axios.delete(`${this.url}/environments/${environment.id}/`);\n }\n\n searchEnvironments(searchPhrases) {\n return axios.get(`${this.url}/environments/filter/?search=${searchPhrases}`);\n }\n\n topics() {\n return axios.get(`${this.url}/topics/`);\n }\n\n searchEnvironmentsByTopic(searchTopicUUID) {\n return axios.get(`${this.url}/environments/filter_topic/?topic=${searchTopicUUID}`);\n }\n\n createImage(file) {\n let formData = new FormData();\n formData.append('file', file);\n return axios.post(`${this.url}/images/`, formData, {\n headers: {\n 'Content-Type': 'multipart/form-data',\n },\n });\n }\n\n myImages() {\n return axios.get(`${this.url}/images/mine/`);\n }\n\n deleteImage(image) {\n return axios.delete(`${this.url}/images/${image.id}/`);\n }\n\n imageConfig() {\n return axios.get(`${this.url}/image_config/`);\n }\n\n repositories() {\n return axios.get(`${this.url}/repositories/`);\n }\n\n myRepositories() {\n return axios.get(`${this.url}/repositories/mine/`);\n }\n\n findGymRepos() {\n return axios.post(`${this.url}/repositories/find_gym_repos/`);\n }\n\n getContributors() {\n return axios.get(`${this.url}/contributors/`);\n }\n\n getProjectAuthors() {\n return axios.get(`${this.url}/projectauthors/`);\n }\n\n getMessageBoards(params) {\n return axios.get(`${this.url}/message_boards/`);\n }\n\n createMessageBoard(messageboard) {\n return axios.post(`${this.url}/message_boards/`, messageboard);\n }\n\n editMessageBoard(messageboard) {\n return axios.put(`${this.url}/message_boards/${messageboard.id}/`, messageboard);\n }\n\n deleteMessageBoard(messageboard) {\n return axios.delete(`${this.url}/message_boards/${messageboard.id}/`);\n }\n\n countComments() {\n return axios.get(`${this.url}/comments/count_comments/`);\n }\n\n getComments(messageboardId) {\n return axios.get(`${this.url}/comments/board_comments/`, { params: { messageboard: messageboardId } });\n }\n\n createComment(comment, messageboardId) {\n return axios.post(`${this.url}/comments/`, {\n comment: comment,\n messageboard: messageboardId,\n });\n }\n\n editComment(commentId, commentText) {\n return axios.put(`${this.url}/comments/${commentId}/`, {\n comment: commentText,\n });\n }\n\n deleteComment(comment) {\n return axios.delete(`${this.url}/comments/${comment.id}/`);\n }\n\n}\n\nexport default new ScigymApiClient(`${getHost()}/api`, 'v1');\n","export function logError(apiError) {\n console.error(apiError);\n}\n","import types from '../utils/types';\n\nimport api from '../utils/api';\nimport { logError } from '../utils/errors';\n\nexport const loginUserWithGithub = (code, state) => {\n return dispatch => {\n dispatch({ type: types.LOGIN_USER_GITHUB_OAUTH, payload: state });\n api\n .login(code)\n .then(response => {\n dispatch({\n type: types.LOGIN_USER_GITHUB_OAUTH_SUCCESS,\n payload: response.data,\n });\n })\n .catch(error => {\n dispatch({ type: types.LOGIN_USER_GITHUB_OAUTH_FAILURE });\n logError(error);\n });\n };\n};\n\nexport const getMyProfile = dispatch => {\n dispatch({ type: types.GET_USER_PROFILE, payload: null });\n api\n .me()\n .then(response => {\n dispatch({\n type: types.GET_USER_PROFILE_SUCCESS,\n payload: response.data,\n });\n })\n .catch(error => {\n dispatch({ type: types.GET_USER_PROFILE_FAILURE, payload: null });\n logError(error);\n });\n};\n\nexport const updateMyProfile = form => {\n return dispatch => {\n dispatch({ type: types.UPDATE_USER_PROFILE, payload: null });\n api\n .updateMe(form)\n .then(response => {\n dispatch({\n type: types.UPDATE_USER_PROFILE_SUCCESS,\n payload: response.data,\n });\n })\n .catch(error => {\n dispatch({ type: types.UPDATE_USER_PROFILE_FAILURE, payload: error });\n logError(error);\n });\n };\n};\n\nexport const deleteUser = () => {\n return dispatch => {\n dispatch({ type: types.DELETE_USER, payload: null });\n api\n .deleteMe()\n .then(response => {\n dispatch({\n type: types.DELETE_USER_SUCCESS,\n payload: response.data,\n });\n })\n .catch(error => {\n dispatch({ type: types.DELETE_USER_FAILURE, payload: error });\n logError(error);\n });\n };\n};\n\nexport const refreshAuthToken = token => {\n return dispatch => {\n dispatch({ type: types.REFRESH_AUTH_TOKEN, payload: null });\n api\n .refreshToken(token)\n .then(response => {\n dispatch({\n type: types.REFRESH_AUTH_TOKEN_SUCCESS,\n payload: response.data,\n });\n getMyProfile(dispatch);\n })\n .catch(error => {\n dispatch({\n type: types.REFRESH_AUTH_TOKEN_FAILURE,\n payload: null,\n });\n logError(error);\n });\n };\n};\n\nexport const logout = clientId => {\n return dispatch => {\n dispatch({ type: types.LOGOUT_USER, payload: null });\n api\n .logout(clientId)\n .then(response => {\n dispatch({\n type: types.LOGOUT_USER_SUCCESS,\n payload: response.data,\n });\n })\n .catch(error => {\n dispatch({\n type: types.LOGOUT_USER_FAILURE,\n payload: null,\n });\n logError(error);\n });\n };\n};\n","import React, { Component } from 'react';\nimport { connect } from 'react-redux';\nimport { Redirect } from 'react-router-dom';\n\nimport { loginUserWithGithub } from '../../actions/user';\n\nexport class Auth extends Component {\n componentWillMount() {\n const code = this.getUrlParams('code');\n const state = this.getUrlParams('state');\n this.props.loginUserWithGithub(code, state);\n }\n\n getUrlParams(prop) {\n let params = {};\n let search = decodeURIComponent(\n window.location.href.slice(window.location.href.indexOf('?') + 1)\n );\n let definitions = search.split('&');\n\n definitions.forEach(function (val, key) {\n let parts = val.split('=', 2);\n params[parts[0]] = parts[1];\n });\n\n return prop && prop in params ? params[prop] : params;\n }\n\n render() {\n const { callbackURL } = this.props\n const url = callbackURL ? '/env/'.concat(callbackURL) : '/';\n return ;\n }\n}\n\nfunction mapStateToProps(state, ownProps) {\n const { callbackURL } = ownProps.match.params;\n return {\n callbackURL: callbackURL,\n };\n};\n\nexport default connect(\n mapStateToProps,\n { loginUserWithGithub }\n)(Auth);\n","import types from '../utils/types';\n\nimport api from '../utils/api';\nimport { logError } from '../utils/errors';\n\nexport const _getUserRepositories = dispatch => {\n dispatch({ type: types.GET_USER_REPOSITORIES_LIST });\n api\n .myRepositories()\n .then(response => {\n dispatch({\n type: types.GET_USER_REPOSITORIES_LIST_SUCCESS,\n payload: response.data,\n });\n })\n .catch(error => {\n dispatch({ type: types.GET_USER_REPOSITORIES_LIST_FAILURE });\n logError(error);\n });\n};\n\nexport const getUserRepositories = () => {\n return _getUserRepositories;\n};\n\nexport const findGymRepos = () => {\n return dispatch => {\n dispatch({ type: types.FIND_GYM_REPOS });\n api\n .findGymRepos()\n .then(response => {\n dispatch({\n type: types.FIND_GYM_REPOS_SUCCESS,\n payload: response.data,\n });\n _getUserRepositories(dispatch);\n })\n .catch(error => {\n dispatch({ type: types.FIND_GYM_REPOS_FAILURE });\n logError(error);\n });\n };\n};\n\nexport const getRepositories = () => {\n return dispatch => {\n dispatch({ type: types.GET_REPOSITORIES });\n api\n .repositories()\n .then(response => {\n dispatch({\n type: types.GET_REPOSITORIES_SUCCESS,\n payload: response.data,\n });\n })\n .catch(error => {\n dispatch({ type: types.GET_REPOSITORIES_FAILURE });\n logError(error);\n });\n };\n};\n","import types from '../utils/types';\n\nimport api from '../utils/api';\nimport { logError } from '../utils/errors';\n\nexport const _getUserImages = dispatch => {\n dispatch({ type: types.GET_USER_IMAGES_LIST });\n api\n .myImages()\n .then(response => {\n dispatch({\n type: types.GET_USER_IMAGES_LIST_SUCCESS,\n payload: response.data,\n });\n })\n .catch(error => {\n dispatch({ type: types.GET_USER_IMAGES_LIST_FAILURE });\n logError(error);\n });\n};\n\nexport const getUserImages = () => {\n return _getUserImages;\n};\n\nexport const _getImageConfig = dispatch => {\n dispatch({ type: types.GET_IMAGE_CONFIG_LIST });\n api\n .imageConfig()\n .then(response => {\n dispatch({\n type: types.GET_IMAGE_CONFIG_LIST_SUCCESS,\n payload: response.data,\n });\n })\n .catch(error => {\n dispatch({ type: types.GET_IMAGE_CONFIG_LIST_FAILURE });\n logError(error);\n });\n};\n\nexport const getImageConfig = () => {\n return _getImageConfig;\n};\n\nexport const createImage = (...args) => {\n return dispatch => {\n dispatch({ type: types.CREATE_IMAGE });\n api\n .createImage(...args)\n .then(response => {\n dispatch({\n type: types.CREATE_IMAGE_SUCCESS,\n payload: response.data,\n });\n api.myImages().then(json => {\n dispatch(getUserImages(json.data));\n });\n })\n .catch(error => {\n dispatch({ type: types.CREATE_IMAGE_FAILURE });\n logError(error);\n });\n };\n};\n\nexport const deleteImage = image => {\n return dispatch => {\n dispatch({\n type: types.DELETE_IMAGE,\n meta: image,\n });\n api\n .deleteImage(image)\n .then(response => {\n dispatch({\n type: types.DELETE_IMAGE_SUCCESS,\n payload: response.data,\n });\n api.myImages().then(json => {\n dispatch(getUserImages(json.data));\n });\n })\n .catch(error => {\n dispatch({ type: types.DELETE_IMAGE_FAILURE });\n logError(error);\n });\n };\n};\n\nexport const resetImageProps = () => {\n return dispatch => {\n dispatch({ type: types.RESET_IMAGE_PROPS });\n };\n};\n","import React, { Component } from 'react';\nimport { connect } from 'react-redux';\n\nimport { getUserRepositories } from '../../actions/repositories';\nimport { getUserImages } from '../../actions/images';\n\nclass UserWatcher extends Component {\n componentDidUpdate(prevProps, prevState) {\n if (!prevProps.userExists && this.props.userExists) {\n this.props.getUserRepositories();\n this.props.getUserImages();\n }\n }\n\n render() {\n return ;\n }\n}\n\nconst mapStateToProps = state => ({\n userExists: state.user.exists,\n});\n\nconst mapDispatchToProps = { getUserRepositories, getUserImages };\n\nexport default connect(\n mapStateToProps,\n mapDispatchToProps\n)(UserWatcher);\n","import React from 'react';\nimport constants from '../../utils/constants';\n\n// TODO: make svg icons and use SvgIcon as in https://v0.material-ui.com/#/components/svg-icon\nexport const Banner = () => (\n \"\"\n);\nexport const SciGymLogo = () => (\n \"\"\n);\nexport const GithubIcon = () => (\n \"\"\n);\nexport const SciGymIcon = () => (\n \n);\nexport const TwitterIcon = () => (\n \"\"\n);\nexport const RLParadigm = () => (\n \"\"\n);\n","import React, { Component } from 'react';\nimport { connect } from 'react-redux';\nimport { compose } from 'redux';\nimport { withStyles } from '@material-ui/core';\nimport { Link } from 'react-router-dom';\nimport PropTypes from 'prop-types';\nimport DialogTitle from '@material-ui/core/DialogTitle';\nimport Dialog from '@material-ui/core/Dialog';\nimport DialogActions from '@material-ui/core/DialogActions';\nimport DialogContent from '@material-ui/core/DialogContent';\nimport DialogContentText from '@material-ui/core/DialogContentText';\nimport Button from '@material-ui/core/Button';\nimport Typography from '@material-ui/core/Typography';\nimport Checkbox from '@material-ui/core/Checkbox';\n\nconst styles = theme => ({\n})\n\nclass LoginForm extends Component {\n constructor(props) {\n super(props);\n this.state = { checked: false };\n }\n get githubOauthLink() {\n const { githubClientId, githubRandomState, callbackURL } = this.props;\n let { githubCallbackUrl } = this.props\n if (callbackURL.length > 0) { githubCallbackUrl = githubCallbackUrl.concat(callbackURL) }\n return `https://github.com/login/oauth/authorize?client_id=${githubClientId}&redirect_uri=${githubCallbackUrl}&state=${githubRandomState}`;\n }\n handleCheck = event => {\n this.setState({ checked: event.target.checked })\n }\n render() {\n const { open, onClose, classes } = this.props;\n const { checked } = this.state;\n return (\n \n {\"Login to SciGym\"}\n \n \n Login via Github to upload and discuss environments on SciGym\n \n \n \n I agree to the Private Policy and Terms and Conditions\n \n \n \n {checked ? (\n \n ) : (\n \n )}\n \n \n )\n }\n}\n\nLoginForm.propTypes = {\n githubClientId: PropTypes.string.isRequired,\n githubCallbackUrl: PropTypes.string.isRequired,\n githubRandomState: PropTypes.string.isRequired,\n classes: PropTypes.object.isRequired,\n open: PropTypes.bool.isRequired,\n onClose: PropTypes.func.isRequired,\n};\n\nconst mapStateToProps = state => {\n return {\n githubClientId: state.config.githubClientId,\n githubCallbackUrl: state.config.githubCallbackUrl,\n githubRandomState: state.config.githubRandomState,\n };\n};\n\nexport default compose(\n connect(mapStateToProps),\n withStyles(styles)\n)(LoginForm);\n","import React, { Component } from 'react';\nimport { connect } from 'react-redux';\nimport PropTypes from 'prop-types';\nimport { Link } from 'react-router-dom';\n\nimport MenuItem from '@material-ui/core/MenuItem';\n\nimport { logout } from '../../actions/user';\n\nclass Logout extends Component {\n constructor(props) {\n super(props);\n this.logoutUser = this.logoutUser.bind(this);\n }\n\n logoutUser() {\n this.props.onClick();\n this.props.logout(this.props.appClientId);\n }\n\n render() {\n return (\n \n Logout\n \n );\n }\n}\n\nLogout.propTypes = {\n logout: PropTypes.func,\n onClick: PropTypes.func.isRequired,\n};\n\nconst mapStateToProps = state => ({\n appClientId: state.config.appClientId,\n});\n\nconst mapDispatchToProps = { logout };\n\nexport default connect(\n mapStateToProps,\n mapDispatchToProps\n)(Logout);\n","import React, { useState } from 'react';\nimport { connect } from 'react-redux';\nimport { compose } from 'redux';\nimport PropTypes from 'prop-types';\nimport { Link } from 'react-router-dom';\n\nimport Button from '@material-ui/core/Button';\nimport ClickAwayListener from '@material-ui/core/ClickAwayListener';\nimport Grow from '@material-ui/core/Grow';\nimport Paper from '@material-ui/core/Paper';\nimport Popper from '@material-ui/core/Popper';\nimport MenuList from '@material-ui/core/MenuList';\nimport MenuItem from '@material-ui/core/MenuItem';\nimport { withStyles } from '@material-ui/core/styles';\nimport AccountCircle from '@material-ui/icons/AccountCircle';\n\nimport LoginForm from '../auth/LoginForm'\nimport Logout from '../auth/Logout';\n\nconst styles = theme => ({\n root: {\n display: 'flex',\n },\n paper: {\n marginRight: theme.spacing.unit * 2,\n },\n});\n\nfunction ProfileMenu({ classes, userExists }) {\n const [open, setOpen] = useState(false);\n const [anchorEl, setAnchorEl] = useState(undefined);\n const [openLogin, setOpenLogin] = useState(false);\n const toggle = () => setOpen(!open);\n const close = () => setOpen(false);\n const toggleLogin = () => {\n setOpen(false);\n setOpenLogin(true)\n }\n const closeLogin = () => setOpenLogin(false);\n return (\n
\n
\n setAnchorEl(node)}\n aria-owns={open ? 'menu-list-grow' : undefined}\n aria-haspopup=\"true\"\n onClick={toggle}\n >\n \n \n \n {({ TransitionProps, placement }) => (\n \n \n \n \n {userExists ? (\n \n My Profile\n \n ) : (\n \n Login\n \n )\n }\n {/* */}\n {userExists && }\n \n About Us\n \n \n \n \n \n )}\n \n
\n \n
\n );\n}\n\nProfileMenu.propTypes = {\n classes: PropTypes.object.isRequired,\n userExists: PropTypes.bool.isRequired,\n};\n\nconst mapStateToProps = state => ({\n userExists: state.user.exists,\n});\n\nexport default compose(\n connect(mapStateToProps),\n withStyles(styles)\n)(ProfileMenu);\n","// Credit David Walsh (https://davidwalsh.name/javascript-debounce-function)\n\n// Returns a function, that, as long as it continues to be invoked, will not\n// be triggered. The function will be called after it stops being called for\n// N milliseconds. If `immediate` is passed, trigger the function on the\n// leading edge, instead of the trailing.\nexport function debounce(func, wait, immediate) {\n let timeout;\n\n return function executedFunction() {\n let context = this;\n let args = arguments;\n\n let later = function() {\n timeout = null;\n if (!immediate) func.apply(context, args);\n };\n\n let callNow = immediate && !timeout;\n\n clearTimeout(timeout);\n\n timeout = setTimeout(later, wait);\n\n if (callNow) func.apply(context, args);\n };\n}\n","import types from '../utils/types';\n\nimport api from '../utils/api';\nimport { logError } from '../utils/errors';\n\nexport const getMessageBoards = params => {\n return dispatch => {\n dispatch({ type: types.GET_MESSAGEBOARDS_LIST });\n api\n .getMessageBoards(params)\n .then(response => {\n dispatch({\n type: types.GET_MESSAGEBOARDS_LIST_SUCCESS,\n payload: response.data,\n });\n })\n .catch(error => {\n dispatch({ type: types.GET_MESSAGEBOARDS_LIST_FAILURE });\n logError(error);\n });\n };\n};\n\nexport const createMessageBoard = (...args) => {\n return dispatch => {\n dispatch({ type: types.CREATE_MESSAGEBOARD });\n api\n .createMessageBoard(...args)\n .then(response => {\n dispatch({\n type: types.CREATE_MESSAGEBOARD_SUCCESS,\n payload: response.data,\n });\n api.getMessageBoards().then(json => {\n dispatch(getMessageBoards(json.data));\n });\n })\n .catch(error => {\n dispatch({ type: types.CREATE_MESSAGEBOARD_FAILURE, payload: error });\n logError(error);\n });\n };\n};\n\nexport const editMessageBoard = (...args) => {\n return dispatch => {\n dispatch({ type: types.EDIT_MESSAGEBOARD });\n api\n .editMessageBoard(...args)\n .then(response => {\n dispatch({\n type: types.EDIT_MESSAGEBOARD_SUCCESS,\n payload: response.data,\n });\n api.getMessageBoards().then(json => {\n dispatch(getMessageBoards(json.data));\n });\n })\n .catch(error => {\n dispatch({ type: types.EDIT_MESSAGEBOARD_FAILURE, payload: error });\n logError(error);\n });\n };\n};\n\nexport const deleteMessageBoard = messageboard => {\n return dispatch => {\n dispatch({\n type: types.DELETE_MESSAGEBOARD,\n meta: messageboard,\n });\n api\n .deleteMessageBoard(messageboard)\n .then(response => {\n dispatch({\n type: types.DELETE_MESSAGEBOARD_SUCCESS,\n payload: response.data,\n });\n api.getMessageBoards().then(json => {\n dispatch(getMessageBoards(json.data));\n });\n })\n .catch(error => {\n dispatch({ type: types.DELETE_MESSAGEBOARD_FAILURE });\n logError(error);\n });\n };\n};\n\nexport const resetMessageBoardsProps = () => {\n return dispatch => {\n dispatch({ type: types.RESET_MESSAGEBOARDS_PROPS });\n };\n};\n\nexport const resetMessageBoardsErrors = () => {\n return dispatch => {\n dispatch({ type: types.RESET_MESSAGEBOARDS_ERRORS });\n };\n};\n\nexport const countComments = () => {\n return dispatch => {\n dispatch({\n type: types.COUNT_COMMENTS,\n });\n api\n .countComments()\n .then(response => {\n dispatch({\n type: types.COUNT_COMMENTS_SUCCESS,\n payload: response.data,\n });\n })\n .catch(error => {\n dispatch({ type: types.COUNT_COMMENTS_FAILURE });\n logError(error);\n });\n }\n}\n\nexport const createReadme = (environment) => {\n const { readme, readmeName } = environment.repository;\n return dispatch => {\n if (readmeName && readmeName.slice(-3) === '.md') {\n const messageboard = { title: 'This is the README', description: atob(readme), environment: environment.id, tags: ['README', 'info'] };\n dispatch(createMessageBoard(messageboard));\n }\n }\n}\n","import types from '../utils/types';\n\nimport api from '../utils/api';\nimport { logError } from '../utils/errors';\nimport { createReadme, getMessageBoards } from './messageboards';\n\nexport const getEnvironments = params => {\n return dispatch => {\n dispatch({ type: types.GET_ENVIRONMENTS_LIST });\n api\n .environments(params)\n .then(response => {\n dispatch({\n type: types.GET_ENVIRONMENTS_LIST_SUCCESS,\n payload: response.data,\n });\n })\n .catch(error => {\n dispatch({ type: types.GET_ENVIRONMENTS_LIST_FAILURE });\n logError(error);\n });\n };\n};\n\nexport const createEnvironment = (...args) => {\n return dispatch => {\n dispatch({ type: types.CREATE_ENVIRONMENT });\n api\n .createEnvironment(...args)\n .then(response => {\n dispatch({\n type: types.CREATE_ENVIRONMENT_SUCCESS,\n payload: response.data,\n });\n api.environments().then(json => {\n dispatch(getEnvironments(json.data));\n dispatch(createReadme(response.data));\n });\n })\n .catch(error => {\n dispatch({ type: types.CREATE_ENVIRONMENT_FAILURE, payload: error });\n logError(error);\n });\n };\n};\n\nexport const editEnvironment = (...args) => {\n return dispatch => {\n dispatch({ type: types.EDIT_ENVIRONMENT });\n api\n .editEnvironment(...args)\n .then(response => {\n dispatch({\n type: types.EDIT_ENVIRONMENT_SUCCESS,\n payload: response.data,\n });\n api.environments().then(json => {\n dispatch(getEnvironments(json.data));\n });\n })\n .catch(error => {\n dispatch({ type: types.EDIT_ENVIRONMENT_FAILURE, payload: error });\n logError(error);\n });\n };\n};\n\nexport const deleteEnvironment = environment => {\n return dispatch => {\n dispatch({\n type: types.DELETE_ENVIRONMENT,\n meta: environment,\n });\n api\n .deleteEnvironment(environment)\n .then(response => {\n dispatch({\n type: types.DELETE_ENVIRONMENT_SUCCESS,\n payload: response.data,\n });\n api.environments().then(json => {\n dispatch(getEnvironments(json.data));\n dispatch(getMessageBoards());\n });\n })\n .catch(error => {\n dispatch({ type: types.DELETE_ENVIRONMENT_FAILURE });\n logError(error);\n });\n };\n};\n\nexport const resetEnvironmentsProps = () => {\n return dispatch => {\n dispatch({ type: types.RESET_ENVIRONMENTS_PROPS });\n };\n};\n\nexport const resetEnvironmentsErrors = () => {\n return dispatch => {\n dispatch({ type: types.RESET_ENVIRONMENTS_ERRORS });\n };\n};\n\nexport const searchEnvironments = searchPhrases => {\n const searchPhrasesSplit = searchPhrases.replace(/[ ,]+/g, ',');\n return dispatch => {\n dispatch({\n type: types.SEARCH_ENVIRONMENTS,\n payload: searchPhrases,\n });\n api\n .searchEnvironments(searchPhrasesSplit)\n .then(response => {\n dispatch({\n type: types.SEARCH_ENVIRONMENTS_SUCCESS,\n payload: response.data,\n });\n })\n .catch(error => {\n dispatch({ type: types.SEARCH_ENVIRONMENTS_FAILURE });\n logError(error);\n });\n };\n};\n\nexport const resetSearchedEnvironments = () => {\n return dispatch => {\n dispatch({ type: types.SEARCH_ENVIRONMENTS_RESET });\n };\n};\n\nexport const searchEnvironmentsByTopic = (searchTopic, topicName) => {\n return dispatch => {\n dispatch({\n type: types.CATEGORIZE_ENVIRONMENTS,\n payload: searchTopic,\n });\n api\n .searchEnvironmentsByTopic(searchTopic)\n .then(response => {\n dispatch({\n type: types.CATEGORIZE_ENVIRONMENTS_SUCCESS,\n environment: response.data,\n topic: { id: searchTopic, name: topicName },\n });\n })\n .catch(error => {\n dispatch({ type: types.CATEGORIZE_ENVIRONMENTS_FAILURE });\n logError(error);\n });\n };\n};\n\nexport const resetCategorizedEnvironments = () => {\n return dispatch => {\n dispatch({ type: types.CATEGORIZE_ENVIRONMENTS_RESET });\n };\n};\n","import React from 'react';\nimport PropTypes from 'prop-types';\nimport { IconButton } from '@material-ui/core';\n\nimport ExpandMoreIcon from '@material-ui/icons/ExpandMore';\nimport ExpandLessIcon from '@material-ui/icons/ExpandLess';\n\nconst ExpandMoreLess = ({\n classes,\n allEnvVisible,\n noEnvVisible,\n handleExpandMore,\n handleExpandLess,\n}) => {\n return (\n
\n {allEnvVisible ? (\n \n \n \n ) : (\n \n \n \n )}\n {noEnvVisible ? (\n \n \n \n ) : (\n \n \n \n )}\n
\n );\n};\n\nExpandMoreLess.propTypes = {\n classes: PropTypes.object.isRequired,\n allEnvVisible: PropTypes.bool.isRequired,\n noEnvVisible: PropTypes.bool.isRequired,\n handleExpandMore: PropTypes.func.isRequired,\n handleExpandLess: PropTypes.func.isRequired,\n};\n\nexport default ExpandMoreLess;\n","import React from 'react';\nimport PropTypes from 'prop-types';\nimport { Link } from 'react-router-dom';\n\nimport MenuItem from '@material-ui/core/MenuItem';\nimport MenuList from '@material-ui/core/MenuList';\nimport ListSubheader from '@material-ui/core/ListSubheader';\nimport Divider from '@material-ui/core/Divider';\n\nconst SearchBarEnvList = ({ environments, handleClose }) => {\n return (\n \n Environment names\n \n }\n >\n \n {environments.length === 0 && (\n \n No environment found\n \n )}\n {environments.length > 0 &&\n environments.map(env => (\n \n {env.name}\n \n ))}\n \n );\n};\n\nSearchBarEnvList.propTypes = {\n environments: PropTypes.arrayOf(PropTypes.object),\n handleClose: PropTypes.func.isRequired,\n};\n\nexport default SearchBarEnvList;\n","import React, { Component } from 'react';\nimport PropTypes from 'prop-types';\nimport { compose } from 'redux';\nimport { connect } from 'react-redux';\n\nimport SearchIcon from '@material-ui/icons/Search';\nimport InputBase from '@material-ui/core/InputBase';\nimport ClickAwayListener from '@material-ui/core/ClickAwayListener';\nimport Grow from '@material-ui/core/Grow';\nimport Paper from '@material-ui/core/Paper';\nimport Popper from '@material-ui/core/Popper';\nimport { withStyles } from '@material-ui/core/styles';\nimport { fade } from '@material-ui/core/styles/colorManipulator';\n\nimport { debounce } from '../../utils/debounce';\nimport { searchEnvironments, resetSearchedEnvironments } from '../../actions/environments';\nimport ExpandMoreLess from '../ExpandMoreLess';\nimport SearchBarEnvList from './SearchBarEnvList';\n\nconst modDisplay = 10;\n\nconst styles = theme => ({\n search: {\n position: 'relative',\n borderRadius: theme.shape.borderRadius,\n backgroundColor: fade(theme.palette.common.white, 0.15),\n '&:hover': {\n backgroundColor: fade(theme.palette.common.white, 0.25),\n },\n marginLeft: theme.spacing.unit * 6,\n width: 'auto',\n [theme.breakpoints.down('xs')]: {\n backgroundColor: fade(theme.palette.primary.main, 0.15),\n '&:hover': {\n backgroundColor: fade(theme.palette.primary.main, 0.25),\n },\n margin: theme.spacing.unit * 2,\n },\n },\n searchIcon: {\n width: theme.spacing.unit * 9,\n height: '100%',\n position: 'absolute',\n pointerEvents: 'none',\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'center',\n [theme.breakpoints.down('xs')]: {\n width: theme.spacing.unit * 4,\n },\n },\n inputRoot: {\n color: 'inherit',\n width: '100%',\n },\n inputInput: {\n paddingTop: theme.spacing.unit,\n paddingRight: theme.spacing.unit,\n paddingBottom: theme.spacing.unit,\n paddingLeft: theme.spacing.unit * 9,\n transition: theme.transitions.create('width'),\n width: '100%',\n [theme.breakpoints.up('md')]: {\n width: 200,\n },\n [theme.breakpoints.down('xs')]: {\n paddingLeft: theme.spacing.unit * 4,\n },\n },\n menuStyle: {\n zIndex: 1,\n minWidth: '200px',\n maxHeight: '200px',\n overflow: 'auto',\n },\n});\n\nclass SearchBar extends Component {\n constructor(props) {\n super(props);\n this.state = {\n searchValue: '',\n open: false,\n visibleEnvironmentCount: modDisplay,\n };\n this.handleSearch = this.handleSearch.bind(this);\n this.delayedSearch = debounce(this.props.searchEnvironments, 500);\n this.delayedReset = debounce(this.props.resetSearchedEnvironments, 500);\n }\n\n handleExpandMore = () => {\n this.setState({ visibleEnvironmentCount: this.state.visibleEnvironmentCount + modDisplay });\n };\n\n handleExpandLess = () => {\n this.setState({ visibleEnvironmentCount: this.state.visibleEnvironmentCount - modDisplay });\n };\n\n handleSearch(event) {\n // TODO: LOADING and FAILURE and X-out button\n event.preventDefault();\n this.setState({ searchValue: event.target.value, open: true });\n if (event.target.value.trim().length >= 3) {\n this.delayedSearch(event.target.value);\n } else {\n this.delayedReset();\n }\n }\n handleClose = () => {\n this.setState({ open: false });\n };\n\n render() {\n const { searchValue, open } = this.state;\n const { classes } = this.props;\n const environments = this.props.searchedEnvironments\n ? this.props.searchedEnvironments\n : this.props.environments;\n const all = true ? this.state.visibleEnvironmentCount >= environments.length : false;\n const none = true ? this.state.visibleEnvironmentCount <= modDisplay : false;\n const visibleEnvironments = environments.slice(0, this.state.visibleEnvironmentCount);\n return (\n
\n \n
\n
\n \n
\n \n \n {({ TransitionProps, placement }) => (\n \n \n \n \n \n \n )}\n \n
\n
\n
\n );\n }\n}\n\nSearchBar.propTypes = {\n searchEnvironments: PropTypes.func.isRequired,\n resetSearchedEnvironments: PropTypes.func.isRequired,\n environments: PropTypes.arrayOf(PropTypes.object),\n environsearchedEnvironmentsments: PropTypes.arrayOf(PropTypes.object),\n};\n\nconst mapDispatchToProps = {\n searchEnvironments,\n resetSearchedEnvironments,\n};\n\nconst mapStateToProps = state => ({\n environments: state.environments.environments,\n searchedEnvironments: state.environments.searchedEnvironments,\n});\n\nexport default compose(\n connect(\n mapStateToProps,\n mapDispatchToProps\n ),\n withStyles(styles)\n)(SearchBar);\n","import React, { PureComponent } from 'react';\nimport { Link } from 'react-router-dom';\nimport { connect } from 'react-redux';\nimport { compose } from 'redux';\nimport { withRouter } from 'react-router-dom'; //use Link instead of href?\nimport PropTypes from 'prop-types';\n\nimport CircularProgress from '@material-ui/core/CircularProgress';\nimport AppBar from '@material-ui/core/AppBar';\nimport IconButton from '@material-ui/core/IconButton';\nimport Star from '@material-ui/icons/Star';\nimport Toolbar from '@material-ui/core/Toolbar';\nimport { withStyles } from '@material-ui/core/styles';\nimport Hidden from '@material-ui/core/Hidden';\n\nimport types from '../../utils/types';\nimport { isLoading } from '../../reducers/display';\nimport { SciGymIcon } from '../files/images';\nimport ProfileMenu from './ProfileMenu';\nimport SearchBar from './SearchBar';\n\nconst styles = theme => ({\n root: {\n flex: 1,\n flexDirection: 'row',\n display: 'flex',\n },\n grow: {\n flexGrow: 1,\n display: 'flex',\n },\n leftIcon: {\n marginRight: theme.spacing.unit,\n },\n menuButton: {\n position: 'relative',\n marginLeft: -12,\n marginRight: 20,\n },\n toolBarStyle: {\n backgroundColor: '#82B1FF',\n },\n iconButtonStyle: {\n borderRadius: '0',\n backgroundColor: 'transparent',\n textDecoration: 'none',\n paddingBottom: '0',\n paddingTop: '0',\n },\n});\n\nexport class Header extends PureComponent {\n render() {\n const { loading } = this.props;\n const { classes } = this.props;\n return (\n \n \n
\n \n \n SciGym\n \n \n \n \n Get Started\n \n \n \n \n \n \n \n
\n \n \n \n
\n
\n {loading ? (\n \n ) : (\n \n )}\n
\n \n \n );\n }\n}\n\nHeader.propTypes = {\n loading: PropTypes.bool.isRequired,\n};\n\nconst mapStateToProps = state => ({\n loading: isLoading(state.display, types.LOGIN_USER_GITHUB_OAUTH),\n});\n\nexport default withRouter(\n compose(\n connect(mapStateToProps),\n withStyles(styles)\n )(Header)\n);\n","import React from 'react';\nimport PropTypes from 'prop-types';\n\nimport Chip from '@material-ui/core/Chip';\n\nimport ErrorOutline from '@material-ui/icons/ErrorOutline';\nimport Done from '@material-ui/icons/Done';\n\nconst VerificationChip = ({ classes, scigym, gym }) => {\n return (\n
\n {scigym ? (\n
\n }\n label=\"SciGym Native\"\n className={classes.tagStyle}\n color=\"primary\"\n />\n
\n ) : gym ? (\n
\n }\n label=\"Gym Verified\"\n className={classes.tagStyle}\n color=\"primary\"\n variant=\"outlined\"\n />\n
\n ) : (\n
\n }\n label=\"Gym Unverified\"\n className={classes.tagStyle}\n color=\"secondary\"\n variant=\"outlined\"\n />\n
\n )}\n
\n );\n};\n\nVerificationChip.propTypes = {\n classes: PropTypes.object.isRequired,\n scigym: PropTypes.bool.isRequired,\n gym: PropTypes.bool.isRequired,\n};\n\nexport default VerificationChip;\n","import React from 'react';\nimport PropTypes from 'prop-types';\n\nimport List from '@material-ui/core/List';\nimport Typography from '@material-ui/core/Typography';\nimport Chip from '@material-ui/core/Chip';\nimport CardContent from '@material-ui/core/CardContent';\n\nimport LocalOffer from '@material-ui/icons/LocalOffer';\n\nconst EnvironmentItemContent = ({ classes, name, description, fork, owner, topic, tags }) => {\n return (\n \n \n {name}\n \n \n {description}\n \n \n Owner: {owner.username} {fork ? (forked) : ''}\n \n \n Category: {topic ? {topic.name} : None }\n \n \n {tags.map(tag => (\n }\n label={tag}\n key={tag}\n clickable\n className={classes.tagStyle}\n color=\"primary\"\n variant=\"outlined\"\n />\n ))}\n \n \n );\n};\n\nEnvironmentItemContent.propTypes = {\n classes: PropTypes.object.isRequired,\n name: PropTypes.string.isRequired,\n description: PropTypes.string,\n fork: PropTypes.bool.isRequired,\n owner: PropTypes.object.isRequired,\n topic: PropTypes.object,\n tags: PropTypes.arrayOf(PropTypes.string),\n};\n\nexport default EnvironmentItemContent;\n","import React, { Component } from 'react';\nimport PropTypes from 'prop-types';\nimport { Link } from 'react-router-dom';\n\nimport ListItem from '@material-ui/core/ListItem';\nimport Card from '@material-ui/core/Card';\nimport CardActions from '@material-ui/core/CardActions';\nimport CardActionArea from '@material-ui/core/CardActionArea';\nimport Button from '@material-ui/core/Button';\nimport { withStyles } from '@material-ui/core';\nimport Slide from '@material-ui/core/Slide';\n\nimport { GithubIcon } from '../../files/images';\nimport constants from '../../../utils/constants';\nimport VerificationChip from '../../VerificationChip';\nimport EnvironmentItemContent from './EnvironmentItemContent';\n\nconst styles = theme => ({\n root: {\n display: 'flex',\n flexFlow: 'row wrap',\n },\n cardStyle: {\n width: '100%',\n },\n logoStyle: {\n flex: '1 1 10px',\n margin: 'auto',\n top: '0',\n bottom: '0',\n textAlign: 'center',\n },\n cardContentStyle: {\n flex: '1 1 400px',\n },\n buttonStyle: {\n margin: theme.spacing.unit,\n [theme.breakpoints.down('xs')]: {\n margin: '0',\n },\n },\n tagStyle: {\n margin: theme.spacing.unit,\n [theme.breakpoints.down('xs')]: {\n margin: '0',\n },\n },\n listStyle: {\n minWidth: '770px',\n [theme.breakpoints.down('xs')]: {\n minWidth: '0px',\n },\n [theme.breakpoints.up('md')]: {\n minWidth: '950px',\n },\n },\n});\n\nclass EnvironmentItem extends Component {\n constructor(props) {\n super(props);\n this.state = {\n open: false,\n };\n }\n\n handleClickOpen = () => {\n const newStateOpen = !this.state.open;\n this.setState({\n open: newStateOpen,\n });\n };\n\n handleClose = () => {\n this.setState({ open: false });\n };\n\n render() {\n const { owner, htmlUrl, gym, fork } = this.props.environment.repository;\n const { name, url, description, scigym, tags, topic, currentAvatar } = this.props.environment;\n const { classes } = this.props;\n let filePath = constants.STATIC_URL + constants.SCIGYM_LOGO;\n if (currentAvatar != null) {\n filePath = constants.MEDIA_URL.concat(currentAvatar.filePath)\n }\n return (\n \n \n \n
\n
\n \"\"\n
\n
\n \n \n \n \n \n \n \n
\n
\n
\n
\n
\n );\n }\n}\n\nEnvironmentItem.propTypes = {\n key: PropTypes.string,\n environment: PropTypes.object.isRequired,\n};\n\nexport default withStyles(styles)(EnvironmentItem);\n","import React from 'react';\nimport PropTypes from 'prop-types';\nimport { Link } from 'react-router-dom';\n\nimport Typography from '@material-ui/core/Typography';\nimport Paper from '@material-ui/core/Paper';\nimport Button from '@material-ui/core/Button';\nimport Hidden from '@material-ui/core/Hidden';\n\nimport { TwitterIcon, GithubIcon } from '../../files/images';\n\nconst HeroOverlay = ({ classes }) => {\n return (\n \n \n Reinforcement Learning for Science\n \n \n \n Welcome to SciGym, the open source library for reinforcement learning environments\n in science.\n \n \n \n \n Welcome to SciGym, the open source library for reinforcement learning environments\n in science.\n \n \n \n
\n \n \n
\n
\n \n Get Started\n \n
\n );\n};\n\nHeroOverlay.propTypes = {\n classes: PropTypes.object.isRequired,\n};\n\nexport default HeroOverlay;\n","import React, { Component } from 'react';\n\nimport { withStyles } from '@material-ui/core';\nimport Typography from '@material-ui/core/Typography';\nimport IconButton from '@material-ui/core/IconButton';\nimport InfoIcon from '@material-ui/icons/Info';\nimport Popover from '@material-ui/core/Popover';\nimport { createMuiTheme, MuiThemeProvider } from '@material-ui/core';\n\nimport constants from '../../../utils/constants';\nimport HeroOverlay from './HeroOverlay';\n\nconst defaultTheme = createMuiTheme();\nconst { breakpoints } = defaultTheme;\n\nconst theme = {\n ...defaultTheme,\n overrides: {\n MuiTypography: {\n h4: {\n fontSize: '2rem',\n [breakpoints.down('xs')]: {\n fontSize: '1.4rem',\n },\n },\n },\n },\n};\n\nconst styles = theme => ({\n hero: {\n position: 'relative',\n height: '400px',\n width: '100%',\n backgroundPosition: '50% 50%',\n backgroundSize: 'cover',\n [theme.breakpoints.down('xs')]: {\n height: '240px',\n },\n [theme.breakpoints.up('lg')]: {\n height: '500px',\n },\n },\n overlay: {\n position: 'absolute',\n width: '50%',\n paddingLeft: theme.spacing.unit * 10,\n paddingTop: theme.spacing.unit * 6,\n paddingBottom: theme.spacing.unit * 10,\n background: 'rgba(255, 255, 255, 0.6)',\n top: '50px',\n [theme.breakpoints.down('xs')]: {\n top: '25px',\n width: '80%',\n paddingLeft: theme.spacing.unit * 2,\n paddingTop: theme.spacing.unit * 2,\n paddingBottom: theme.spacing.unit * 2,\n },\n [theme.breakpoints.up('lg')]: {\n top: '75px',\n width: '25%',\n },\n },\n titleStyle: {\n margin: theme.spacing.unit,\n },\n textStyle: {\n margin: theme.spacing.unit,\n },\n buttonStyle: {\n position: 'absolute',\n margin: theme.spacing.unit,\n right: '20px',\n },\n mediaButtons: {\n position: 'absolute',\n },\n iconStyle: {\n position: 'absolute',\n right: '10px',\n top: '10px',\n },\n popContentStyle: {\n margin: theme.spacing.unit,\n },\n});\n\nclass Hero extends Component {\n state = {\n anchorEl: null,\n };\n handleClick = event => {\n this.setState({\n anchorEl: event.currentTarget,\n });\n };\n\n handleClose = () => {\n this.setState({\n anchorEl: null,\n });\n };\n render() {\n const { anchorEl } = this.state;\n const { classes } = this.props;\n const open = Boolean(anchorEl);\n return (\n \n \n \n \n \n \n \n \n Image Credit: University of Innsbruck/Harald Ritsch\n \n \n
\n \n );\n }\n}\n\nexport default withStyles(styles)(Hero);\n","import React from 'react';\nimport { Link } from 'react-router-dom';\n\nimport { withStyles } from '@material-ui/core';\nimport Divider from '@material-ui/core/Divider';\nimport Button from '@material-ui/core/Button';\n\nimport { SciGymLogo } from '../../files/images';\n\nconst styles = theme => ({\n root: {\n flex: '1',\n textAlign: 'center',\n },\n logoStyle: {\n margin: 'auto',\n marginTop: theme.spacing.unit * 3,\n marginBottom: theme.spacing.unit * 3,\n },\n});\n\nconst DrawerHead = props => {\n const { classes } = props;\n return (\n
\n \n \n
\n );\n};\n\nexport default withStyles(styles)(DrawerHead);\n","import React from 'react';\nimport PropTypes from 'prop-types';\n\nimport List from '@material-ui/core/List';\nimport ListItem from '@material-ui/core/ListItem';\nimport ListItemText from '@material-ui/core/ListItemText';\n\nconst ChildListElement = ({ topic, handleClick }) => {\n return (\n \n \n \n \n \n );\n};\n\nChildListElement.propTypes = {\n topic: PropTypes.object,\n handleClick: PropTypes.func.isRequired,\n};\n\nexport default ChildListElement;\n","import React from 'react';\nimport PropTypes from 'prop-types';\n\nimport ListItem from '@material-ui/core/ListItem';\nimport ListItemText from '@material-ui/core/ListItemText';\nimport Divider from '@material-ui/core/Divider';\nimport ExpandLess from '@material-ui/icons/ExpandLess';\nimport ExpandMore from '@material-ui/icons/ExpandMore';\nimport Collapse from '@material-ui/core/Collapse';\n\nimport ChildListElement from './ChildListElement';\n\nconst DrawerContentTopics = ({ parentTopics, childTopics, open, handleTopClick, handleClick }) => {\n return (\n
\n {parentTopics.map((parentTopic, index) => (\n
\n handleTopClick(index, parentTopic.id, parentTopic.name)}\n >\n \n {open[index] ? : }\n \n \n {childTopics.map(\n topic =>\n topic.parentTopic.id === parentTopic.id && (\n handleClick(topic.id, topic.name)}\n />\n )\n )}\n \n \n
\n ))}\n
\n );\n};\n\nDrawerContentTopics.propTypes = {\n parentTopics: PropTypes.arrayOf(PropTypes.object),\n childTopics: PropTypes.arrayOf(PropTypes.object),\n open: PropTypes.arrayOf(PropTypes.bool).isRequired,\n handleTopClick: PropTypes.func.isRequired,\n handleClick: PropTypes.func.isRequired,\n};\n\nexport default DrawerContentTopics;\n","import React, { Component } from 'react';\nimport { connect } from 'react-redux';\nimport { compose } from 'redux';\nimport PropTypes from 'prop-types';\n\nimport ListItem from '@material-ui/core/ListItem';\nimport ListItemText from '@material-ui/core/ListItemText';\nimport MenuList from '@material-ui/core/MenuList';\nimport Divider from '@material-ui/core/Divider';\nimport { withStyles } from '@material-ui/core';\nimport Typography from '@material-ui/core/Typography';\nimport Hidden from '@material-ui/core/Hidden';\n\nimport {\n searchEnvironmentsByTopic,\n resetCategorizedEnvironments,\n} from '../../../actions/environments';\nimport DrawerHead from './DrawerHead';\nimport SearchBar from '../../header/SearchBar';\nimport DrawerContentTopics from './DrawerContentTopics';\n\nconst drawerWidth = 240;\nconst styles = theme => ({\n root: {\n width: drawerWidth,\n flexShrink: 0,\n paddingTop: '0',\n paddingBottom: '0',\n },\n title: {\n margin: theme.spacing.unit * 2,\n marginTop: theme.spacing.unit * 6,\n },\n});\n\nclass DrawerContent extends Component {\n constructor(props) {\n super(props);\n const parentTopics = this.props.topics.filter(topic => !topic.parentTopic);\n this.state = {\n openTopicList: Array.apply(null, Array(parentTopics.length)).map(() => false),\n error: '',\n };\n }\n componentDidUpdate(prevProps) {\n if (this.props.topics.length > prevProps.topics.length) {\n const parentTopics = this.props.topics.filter(topic => !topic.parentTopic);\n this.setState({\n openTopicList: Array.apply(null, Array(parentTopics.length)).map(() => false),\n });\n }\n }\n\n handleTopClick = (index, id, name) => {\n //TODO: handle FAILURE\n const openList = this.state.openTopicList;\n openList[index] = !openList[index];\n this.setState({ openTopicList: openList });\n this.props.searchEnvironmentsByTopic(id, name);\n };\n\n handleClick = (id, name) => {\n //TODO: handle FAILURE\n if (id === 'all') {\n this.props.resetCategorizedEnvironments();\n } else this.props.searchEnvironmentsByTopic(id, name);\n };\n render() {\n const { classes, topics } = this.props;\n const parentTopics = topics.filter(topic => !topic.parentTopic);\n const childTopics = topics.filter(topic => topic.parentTopic);\n return (\n
\n \n \n \n \n \n Search Categories\n \n \n this.handleClick('all', undefined)}>\n \n \n \n \n \n
\n );\n }\n}\nDrawerContent.propTypes = {\n topics: PropTypes.arrayOf(PropTypes.object),\n searchEnvironmentsByTopic: PropTypes.func.isRequired,\n resetCategorizedEnvironments: PropTypes.func.isRequired,\n};\n\nconst mapDispatchToProps = {\n searchEnvironmentsByTopic,\n resetCategorizedEnvironments,\n};\n\nexport default compose(\n connect(\n null,\n mapDispatchToProps\n ),\n withStyles(styles)\n)(DrawerContent);\n","import React, { Component } from 'react';\nimport { connect } from 'react-redux';\nimport { compose } from 'redux';\nimport PropTypes from 'prop-types';\n\nimport Paper from '@material-ui/core/Paper';\nimport { withStyles } from '@material-ui/core';\nimport Hidden from '@material-ui/core/Hidden';\nimport SwipeableDrawer from '@material-ui/core/SwipeableDrawer';\n\nimport DrawerContent from './DrawerContent';\n\nconst drawerWidth = 240;\n\nconst styles = () => ({\n drawerPaper: {\n width: drawerWidth,\n flexShrink: 0,\n paddingTop: '0',\n paddingBottom: '0',\n height: '100%',\n },\n});\n\nclass TopicDrawer extends Component {\n constructor(props) {\n super(props);\n this.state = {\n openDrawer: false,\n };\n }\n\n toggleDrawer = open => event => {\n if (event && event.type === 'keydown' && (event.key === 'Tab' || event.key === 'Shift')) {\n return;\n }\n this.setState({ openDrawer: open });\n };\n\n render() {\n const { classes, topics } = this.props;\n return (\n
\n \n \n \n \n \n \n \n \n \n \n
\n );\n }\n}\n\nTopicDrawer.propTypes = {\n topics: PropTypes.arrayOf(PropTypes.object),\n};\n\nconst mapStateToProps = state => ({\n topics: state.topics.topics,\n});\n\nexport default compose(\n connect(mapStateToProps),\n withStyles(styles)\n)(TopicDrawer);\n","import React from 'react';\nimport PropTypes from 'prop-types';\n\nimport Typography from '@material-ui/core/Typography';\nimport Divider from '@material-ui/core/Divider';\nimport Hidden from '@material-ui/core/Hidden';\n\nimport constants from '../../../utils/constants';\n\nconst featureCardAPI = ({ classes }) => {\n return (\n
\n
\n \n Science Problems packaged as APIs\n \n \n In Reinforcement Learning an agent interacts with an environment to achieve some goal. Our\n environments encode problems in science packaged as APIs.\n \n
\n
\n \n
\n \n \n \n
\n );\n};\n\nfeatureCardAPI.propTypes = {\n classes: PropTypes.object.isRequired,\n};\n\nexport default featureCardAPI;\n","import React from 'react';\nimport PropTypes from 'prop-types';\n\nimport Typography from '@material-ui/core/Typography';\nimport Divider from '@material-ui/core/Divider';\nimport Hidden from '@material-ui/core/Hidden';\n\nimport constants from '../../../utils/constants';\n\nconst featureCardRL = ({ classes }) => {\n return (\n
\n
\n \n Reinforcement Learning for Science\n \n \n SciGym is a resource for facilitating the development of reinforcement learning based\n solutions to problems in physics and other sciences.\n \n
\n
\n \n
\n \n \n \n
\n );\n};\n\nfeatureCardRL.propTypes = {\n classes: PropTypes.object.isRequired,\n};\n\nexport default featureCardRL;\n","import React from 'react';\nimport PropTypes from 'prop-types';\n\nimport Typography from '@material-ui/core/Typography';\n\nimport constants from '../../../utils/constants';\n\nconst featureCardRL = ({ classes }) => {\n return (\n
\n
\n \n Connecting Computer Science and other Disciplines\n \n \n SciGym is an attempt to stimulate an open and meaningful exchange between computer\n scientists and researchers in other disciplines.\n \n
\n
\n \n
\n
\n );\n};\n\nfeatureCardRL.propTypes = {\n classes: PropTypes.object.isRequired,\n};\n\nexport default featureCardRL;\n","import React, { Component } from 'react';\n\nimport { withStyles } from '@material-ui/core';\nimport Grid from '@material-ui/core/Grid';\n\nimport FeatureCardAPI from './FeatureCardAPI';\nimport FeatureCardRL from './FeatureCardRL';\nimport FeatureCardConnect from './FeatureCardConnect';\n\nconst styles = theme => ({\n cardStyle: {\n width: '300px',\n marginLeft: theme.spacing.unit * 2,\n marginRight: theme.spacing.unit * 2,\n },\n mediaStyle: {\n textAlign: 'center',\n marginTop: theme.spacing.unit,\n height: '160px',\n },\n contentStyle: {\n height: '170',\n },\n titleStyle: {\n marginTop: theme.spacing.unit,\n marginBottom: theme.spacing.unit,\n },\n imgStyleAPI: {\n height: '120px',\n width: '152px',\n },\n imgStyleRL: {\n paddingTop: '10px',\n height: '140px',\n width: '140px',\n },\n imgStyleConnect: {\n paddingTop: '10px',\n height: '140px',\n width: '127px',\n },\n});\n\nclass FeatureCards extends Component {\n render() {\n const { classes } = this.props;\n return (\n \n \n \n \n \n \n \n \n \n \n \n );\n }\n}\n\nexport default withStyles(styles)(FeatureCards);\n","import React, { Component } from 'react';\nimport { connect } from 'react-redux';\nimport { compose } from 'redux';\nimport PropTypes from 'prop-types';\n\nimport List from '@material-ui/core/List';\nimport Divider from '@material-ui/core/Divider';\nimport Typography from '@material-ui/core/Typography';\nimport Grid from '@material-ui/core/Grid';\nimport { withStyles } from '@material-ui/core';\n\nimport EnvironmentItem from './environment_item/EnvironmentItem';\nimport Hero from './hero/Hero';\nimport TopicDrawer from './drawer/TopicDrawer';\nimport FeatureCards from './feature_cards/FeatureCards';\nimport ExpandMoreLess from '../ExpandMoreLess';\n\nconst modDisplay = 10;\n\nconst styles = theme => ({\n root: {\n flexGrow: 1,\n backgroundColor: 'AliceBlue',\n },\n title: {\n margin: theme.spacing.unit * 2,\n marginTop: theme.spacing.unit * 6,\n },\n wrapper: {\n display: 'flex',\n flexFlow: 'row nowrap',\n },\n gridStyle: {\n [theme.breakpoints.up('lg')]: {\n width: '80%',\n },\n },\n emptyStyle: {\n minWidth: '770px',\n [theme.breakpoints.down('xs')]: {\n minWidth: '0px',\n },\n [theme.breakpoints.up('md')]: {\n minWidth: '950px',\n },\n },\n buttonStyle: {\n left: '40%',\n },\n buttonPos: {\n minWidth: '770px',\n [theme.breakpoints.down('xs')]: {\n minWidth: '0px',\n },\n [theme.breakpoints.up('md')]: {\n minWidth: '950px',\n },\n },\n});\n\nclass Home extends Component {\n constructor(props) {\n super(props);\n this.state = {\n visibleEnvironmentCount: modDisplay,\n };\n }\n\n handleExpandMore = () => {\n this.setState({ visibleEnvironmentCount: this.state.visibleEnvironmentCount + modDisplay });\n };\n\n handleExpandLess = () => {\n this.setState({ visibleEnvironmentCount: this.state.visibleEnvironmentCount - modDisplay });\n };\n\n get title() {\n if (this.props.categorizedEnvironments) {\n const title = this.props.searchedTopic.name;\n return title;\n }\n return 'Recent environments';\n }\n render() {\n const { classes } = this.props;\n const environments = this.props.categorizedEnvironments\n ? this.props.categorizedEnvironments\n : this.props.environments;\n const empty = environments.length === 0;\n const all = true ? this.state.visibleEnvironmentCount >= environments.length : false;\n const none = true ? this.state.visibleEnvironmentCount <= modDisplay : false;\n const visibleEnvironments = environments.slice(0, this.state.visibleEnvironmentCount);\n return (\n
\n \n
\n \n \n
\n {!this.props.categorizedEnvironments && (\n
\n \n Features & Goals\n \n \n
\n )}\n \n {this.title}\n \n {empty && (\n
\n \n No environments found\n \n
\n )}\n {!empty && (\n \n {visibleEnvironments.map(env => (\n \n \n \n \n ))}\n \n )}\n \n
\n
\n
\n
\n );\n }\n}\n\nHome.propTypes = {\n repositories: PropTypes.arrayOf(PropTypes.object),\n environments: PropTypes.arrayOf(PropTypes.object),\n categorizedEnvironments: PropTypes.arrayOf(PropTypes.object),\n searchedTopics: PropTypes.arrayOf(PropTypes.object),\n};\n\nconst mapStateToProps = state => ({\n repositories: state.repositories.repositories,\n environments: state.environments.environments,\n categorizedEnvironments: state.environments.categorizedEnvironments,\n searchedTopic: state.environments.searchedTopic,\n});\n\nexport default compose(\n connect(mapStateToProps),\n withStyles(styles)\n)(Home);\n","import React, { useState } from 'react';\nimport { connect } from 'react-redux';\nimport { compose } from 'redux';\nimport PropTypes from 'prop-types';\n\nimport { withStyles } from '@material-ui/core/styles';\nimport TextField from '@material-ui/core/TextField';\nimport Button from '@material-ui/core/Button';\nimport SaveIcon from '@material-ui/icons/Save';\n\nimport types from '../../../utils/types';\nimport { isLoading } from '../../../reducers/display';\nimport { getErrors } from '../../../reducers/errors';\nimport { updateMyProfile } from '../../../actions/user';\n\nconst styles = theme => ({\n root: {\n display: 'flex',\n flexFlow: 'column nowrap',\n paddingBottom: theme.spacing.unit * 4,\n },\n textField: {\n width: 300,\n },\n button: {\n marginTop: theme.spacing.unit,\n width: 250,\n },\n rightIcon: {\n marginLeft: theme.spacing.unit,\n },\n});\n\nconst AccountForm = ({ classes, user, updateMyProfile, isUpdating, errors }) => {\n const [email, setEmail] = useState(user.email);\n const [firstName, setFirstName] = useState(user.firstName);\n const [lastName, setLastName] = useState(user.lastName);\n\n const saveForm = () => updateMyProfile({ email, firstName, lastName });\n\n return (\n
\n \n setEmail(event.target.value)}\n error={Boolean(errors && errors.email)}\n helperText={errors && errors.email}\n />\n setFirstName(event.target.value)}\n error={Boolean(errors && errors.firstName)}\n helperText={errors && errors.firstName}\n />\n setLastName(event.target.value)}\n error={Boolean(errors && errors.lastName)}\n helperText={errors && errors.lastName}\n />\n \n
\n );\n};\n\nAccountForm.propTypes = {\n classes: PropTypes.object.isRequired,\n user: PropTypes.object.isRequired,\n updateMyProfile: PropTypes.func.isRequired,\n isUpdating: PropTypes.bool.isRequired,\n};\n\nconst mapStateToProps = state => ({\n user: state.user,\n isUpdating: isLoading(state.display, types.UPDATE_USER_PROFILE),\n errors: getErrors(state.errors, types.UPDATE_USER_PROFILE),\n});\n\nexport default compose(\n connect(\n mapStateToProps,\n { updateMyProfile }\n ),\n withStyles(styles)\n)(AccountForm);\n","import React, { useState } from 'react';\nimport { connect } from 'react-redux';\n\nimport Button from '@material-ui/core/Button';\nimport TextField from '@material-ui/core/TextField';\nimport Dialog from '@material-ui/core/Dialog';\nimport DialogActions from '@material-ui/core/DialogActions';\nimport DialogContent from '@material-ui/core/DialogContent';\nimport DialogContentText from '@material-ui/core/DialogContentText';\nimport DialogTitle from '@material-ui/core/DialogTitle';\n\nimport { deleteUser } from '../../../actions/user';\n\nconst AccountDelete = ({ userName, open, handleClose, deleteUser }) => {\n const [confirmationUserName, setConfirmationUsername] = useState('');\n const confirmationMatches = confirmationUserName === userName;\n const confirmDelete = () => {\n deleteUser();\n handleClose();\n };\n return (\n \n Delete Account Confirmation\n \n \n Confirm account deletion by entering your username below\n \n setConfirmationUsername(event.target.value)}\n />\n \n \n \n \n \n \n );\n};\n\nconst mapStateToProps = state => ({\n userName: state.user.username,\n});\n\nexport default connect(\n mapStateToProps,\n { deleteUser }\n)(AccountDelete);\n","import React, { useState } from 'react';\nimport PropTypes from 'prop-types';\n\nimport { withStyles } from '@material-ui/core/styles';\nimport Button from '@material-ui/core/Button';\nimport DeleteIcon from '@material-ui/icons/Delete';\nimport Typography from '@material-ui/core/Typography';\n\nimport AccountDelete from './AccountDelete';\n\nconst styles = theme => ({\n root: {\n paddingTop: theme.spacing.unit * 4,\n paddingBottom: theme.spacing.unit * 2,\n },\n button: {\n marginTop: theme.spacing.unit,\n width: 250,\n },\n rightIcon: {\n marginLeft: theme.spacing.unit,\n },\n});\n\nconst Account = ({ classes }) => {\n const [showConfirm, setShowConfirm] = useState(false);\n return (\n
\n setShowConfirm(false)} />\n \n Delete Account\n \n \n This action will permanently delete your account. This cannot be undone!\n \n setShowConfirm(true)}\n >\n Delete my account\n \n \n
\n );\n};\n\nAccount.propTypes = {\n classes: PropTypes.object.isRequired,\n};\n\nexport default withStyles(styles)(Account);\n","import React from 'react';\nimport PropTypes from 'prop-types';\n\nimport { withStyles } from '@material-ui/core/styles';\nimport Divider from '@material-ui/core/Divider';\nimport Typography from '@material-ui/core/Typography';\n\nimport AccountForm from './AccountForm';\nimport AccountDanger from './AccountDanger';\n\nconst styles = theme => ({\n root: {\n ...theme.mixins.gutters(),\n paddingTop: theme.spacing.unit * 2,\n paddingBottom: theme.spacing.unit * 2,\n marginLeft: theme.spacing.unit * 2,\n },\n});\n\nfunction Account(props) {\n const { classes } = props;\n\n return (\n
\n \n Update Account Details\n \n \n \n \n
\n );\n}\n\nAccount.propTypes = {\n classes: PropTypes.object.isRequired,\n};\n\nexport default withStyles(styles)(Account);\n","import React from 'react';\nimport { Link } from 'react-router-dom';\nimport PropTypes from 'prop-types';\n\nimport MenuItem from '@material-ui/core/MenuItem';\nimport { withStyles } from '@material-ui/core/styles';\nimport ListItemIcon from '@material-ui/core/ListItemIcon';\nimport ListItemText from '@material-ui/core/ListItemText';\n\nconst styles = theme => ({\n menuItem: {\n '&:focus': {\n backgroundColor: '#82B1FF',\n },\n },\n});\n\nfunction SettingsItem({ classes, to, icon, text }) {\n return (\n \n {icon}\n \n \n );\n}\n\nSettingsItem.propTypes = {\n classes: PropTypes.object.isRequired,\n to: PropTypes.string.isRequired,\n text: PropTypes.string.isRequired,\n};\n\nexport default withStyles(styles)(SettingsItem);\n","import React, { useState } from 'react';\nimport { withStyles } from '@material-ui/core';\n\nimport MenuList from '@material-ui/core/MenuList';\nimport Paper from '@material-ui/core/Paper';\nimport Divider from '@material-ui/core/Divider';\nimport AccountIcon from '@material-ui/icons/AccountBox';\nimport ForumIcon from '@material-ui/icons/Forum';\nimport ListIcon from '@material-ui/icons/ViewList';\nimport ImageIcon from '@material-ui/icons/Image';\nimport Hidden from '@material-ui/core/Hidden';\nimport SwipeableDrawer from '@material-ui/core/SwipeableDrawer';\n\nimport SettingsItem from './SettingsItem';\nimport DrawerHead from '../home/drawer/DrawerHead';\n\nconst drawerWidth = 240;\nconst styles = theme => ({\n drawerPaper: {\n width: drawerWidth,\n flexShrink: 0,\n paddingTop: '0',\n paddingBottom: '0',\n height: '100%',\n },\n});\n\nfunction Settings({ classes }) {\n const [open, setOpen] = useState(false);\n const toggle = openDrawer => event => {\n if (event && event.type === 'keydown' && (event.key === 'Tab' || event.key === 'Shift')) {\n return;\n }\n setOpen(openDrawer);\n };\n return (\n
\n \n \n \n \n } />\n \n } />\n } />\n } />\n \n \n \n \n \n
\n \n \n } />\n \n } />\n } />\n } />\n \n
\n
\n
\n
\n );\n}\n\nSettings.propTypes = {};\n\nexport default withStyles(styles)(Settings);\n","import React, { Component } from 'react';\nimport PropTypes from 'prop-types';\n\nimport { withStyles } from '@material-ui/core';\nimport Grid from '@material-ui/core/Grid';\nimport Typography from '@material-ui/core/Typography';\nimport Card from '@material-ui/core/Card';\nimport CardMedia from '@material-ui/core/CardMedia';\nimport Radio from '@material-ui/core/Radio';\n\nimport constants from '../../../utils/constants';\n\nconst styles = theme => ({\n root: {\n margin: 'auto',\n height: '155px',\n paddingLeft: '5%',\n paddingRight: '5%',\n backgroundColor: 'AliceBlue',\n paddingTop: theme.spacing.unit * 3,\n maxWidth: '500px',\n [theme.breakpoints.down('sm')]: {\n paddingLeft: '5%',\n paddingRight: '5%',\n },\n [theme.breakpoints.up('lg')]: {\n paddingLeft: '5%',\n paddingRight: '5%',\n },\n },\n gridStyle: {\n overflow: 'auto',\n overflowX: 'scroll',\n flexWrap: 'nowrap',\n // Promote the list into his own layer on Chrome. This cost memory but helps keeping high FPS.\n transform: 'translateZ(0)',\n },\n cardStyle: {\n width: '100px',\n },\n mediaStyle: {\n height: '100px',\n },\n textStyle: {\n margin: theme.spacing.unit,\n },\n});\n\nclass ImagePopContent extends Component {\n constructor(props) {\n super(props);\n this.state = {\n selectedAvatar: props.avatar,\n };\n this.handleChange = this.handleChange.bind(this);\n }\n\n handleChange = avatar => event => {\n if (event.target.value !== 'default') {\n this.setState({ selectedAvatar: avatar });\n this.props.handleSelect(avatar);\n } else {\n this.setState({ selectedAvatar: null });\n this.props.handleSelect(null);\n }\n };\n\n render() {\n const { classes, userImages } = this.props;\n if (userImages.length > 0) {\n return (\n
\n \n \n \n \n \n \n \n \n {userImages.map(image => (\n \n \n \n \n \n \n \n ))}\n \n
\n );\n } else {\n return (\n \n You don't have any images, yet!\n \n );\n }\n }\n}\n\nImagePopContent.propTypes = {\n userImages: PropTypes.arrayOf(PropTypes.object).isRequired,\n handleSelect: PropTypes.func.isRequired,\n avatar: PropTypes.any, // this is null or an object\n};\n\nexport default withStyles(styles)(ImagePopContent);\n","import React, { Component } from 'react';\nimport PropTypes from 'prop-types';\nimport { connect } from 'react-redux';\nimport { compose } from 'redux';\n\nimport { withStyles } from '@material-ui/core';\nimport Typography from '@material-ui/core/Typography';\nimport Popover from '@material-ui/core/Popover';\nimport Button from '@material-ui/core/Button';\nimport CircularProgress from '@material-ui/core/CircularProgress';\n\nimport constants from '../../../utils/constants';\nimport ImagePopContent from './ImagePopContent';\nimport { createImage } from '../../../actions/images';\nimport { isLoading } from '../../../reducers/display';\nimport types from '../../../utils/types';\n\nconst styles = theme => ({\n root: {\n display: 'flex',\n flexFlow: 'row wrap',\n },\n logoStyle: {\n flex: '1 1 10px',\n margin: theme.spacing.unit,\n top: '0',\n bottom: '0',\n textAlign: 'center',\n },\n contentStyle: {\n margin: theme.spacing.unit,\n },\n imageUploadText: {\n marginTop: theme.spacing.unit * 6,\n marginBottom: theme.spacing.unit,\n [theme.breakpoints.down('xs')]: {\n margin: theme.spacing.unit,\n },\n },\n loadingStyle: {\n padding: '70px 0',\n },\n errorStyle: {\n margin: theme.spacing.unit * 2,\n },\n});\n\nconst MAX_FILE_SIZE_bytes = 2000000;\n\nclass ImagePreview extends Component {\n constructor(props) {\n super(props);\n let filePath = constants.SCIGYM_LOGO;\n if (props.avatar != null) {\n filePath = props.avatar.filePath;\n }\n this.state = {\n avatar: props.avatar,\n avatarURL: constants.MEDIA_URL.concat(filePath),\n error: null,\n anchorEl: null,\n };\n\n this.handleChange = this.handleChange.bind(this);\n }\n\n componentDidUpdate(prevProps) {\n if (prevProps.avatar !== this.props.avatar) {\n let filePath = constants.SCIGYM_LOGO;\n if (this.props.avatar != null) {\n filePath = this.props.avatar.filePath;\n }\n this.setState({\n avatar: this.props.avatar,\n avatarURL: constants.MEDIA_URL.concat(filePath),\n error: null,\n });\n }\n }\n\n handleClick = event => {\n this.setState({\n anchorEl: event.currentTarget,\n });\n };\n\n handleClose = () => {\n this.setState({\n anchorEl: null,\n });\n };\n\n handleSelect = selectedAvatar => {\n this.props.handleSelect(selectedAvatar);\n };\n\n handleChange = imageConfig => event => {\n event.preventDefault();\n const uploadedFile = event.target.files[0];\n const fileExtension = uploadedFile.name.split('.').pop().toLowerCase();\n\n console.log(uploadedFile.size);\n //2MB limit & specific file extension\n let error;\n if (uploadedFile.size > MAX_FILE_SIZE_bytes) {\n error = `Sorry, avatar size is limited to 2MB.`;\n } else if (!imageConfig.includes('.'.concat(fileExtension))) {\n error = `Incorrect file extension. we only accept ${imageConfig.join()}`;\n }\n\n if (error) {\n this.setState({ error });\n } else {\n this.props.createImage(uploadedFile);\n }\n };\n\n render() {\n const { error, anchorEl } = this.state;\n const { classes, userImages, imageConfig, loading } = this.props;\n const open = Boolean(anchorEl);\n return (\n
\n
\n {loading ? (\n \n ) : (\n \"\"\n )}\n
\n
\n \n My Images\n \n \n Upload new image\n \n \n
\n \n \n \n {error ? (\n \n {error}\n \n ) : null}\n
\n );\n }\n}\n\nImagePreview.propTypes = {\n avatar: PropTypes.any, // this is null or an object\n userImages: PropTypes.arrayOf(PropTypes.object),\n handleSelect: PropTypes.func.isRequired,\n imageConfig: PropTypes.arrayOf(PropTypes.string),\n createImage: PropTypes.func.isRequired,\n loading: PropTypes.bool.isRequired,\n};\n\nconst mapStateToProps = state => ({\n userImages: state.images.userImages,\n imageConfig: state.images.imageConfig,\n loading: isLoading(state.display, types.CREATE_IMAGE),\n});\nconst mapDispatchToProps = {\n createImage,\n};\n\nexport default compose(\n connect(\n mapStateToProps,\n mapDispatchToProps\n ),\n withStyles(styles)\n)(ImagePreview);\n","import React from 'react';\nimport PropTypes from 'prop-types';\nimport { withStyles } from '@material-ui/core';\n\nimport TextField from '@material-ui/core/TextField';\n\nconst styles = theme => ({\n root: {\n width: '100%',\n },\n child: {\n width: 'auto',\n margin: theme.spacing.unit * 2,\n },\n textField: {\n width: '100%',\n },\n});\n\nconst EnvironmentFormText = ({ classes, name, description, repository, handleChange, errors }) => {\n return (\n
\n
\n \n
\n
\n \n
\n
\n \n
\n
\n );\n};\n\nEnvironmentFormText.propTypes = {\n classes: PropTypes.object.isRequired,\n name: PropTypes.string.isRequired,\n description: PropTypes.string,\n repository: PropTypes.object.isRequired,\n handleChange: PropTypes.func.isRequired,\n errors: PropTypes.any, // this is either bool or object\n};\n\nexport default withStyles(styles)(EnvironmentFormText);\n","import React from 'react';\nimport PropTypes from 'prop-types';\nimport { withStyles } from '@material-ui/core';\n\nimport FormControl from '@material-ui/core/FormControl';\nimport FormHelperText from '@material-ui/core/FormHelperText';\nimport Select from '@material-ui/core/Select';\nimport MenuItem from '@material-ui/core/MenuItem';\nimport Input from '@material-ui/core/Input';\nimport InputLabel from '@material-ui/core/InputLabel';\nimport InputAdornment from '@material-ui/core/InputAdornment';\nimport IconButton from '@material-ui/core/IconButton';\n\nimport AddIcon from '@material-ui/icons/Add';\n\nconst styles = theme => ({\n root: {\n width: '100%',\n },\n child: {\n width: 'auto',\n margin: theme.spacing.unit * 2,\n },\n textField: {\n width: '100%',\n },\n});\n\nconst EnvironmentFormControl = ({\n classes,\n topics,\n topic,\n tag,\n handleChangeTopic,\n handleChange,\n handleAddTag,\n errors,\n}) => {\n return (\n
\n
\n \n Category\n \n \n None\n \n {topics.map(topic => (\n \n {topic.name}\n \n ))}\n \n \n
\n
\n \n Add a Tag\n \n \n \n \n \n }\n error={Boolean(errors && errors.tags)}\n />\n \n {errors && errors.tags}\n \n \n
\n
\n );\n};\n\nEnvironmentFormControl.propTypes = {\n classes: PropTypes.object.isRequired,\n topics: PropTypes.arrayOf(PropTypes.object),\n topic: PropTypes.string,\n tag: PropTypes.string,\n handleChangeTopic: PropTypes.func.isRequired,\n handleChange: PropTypes.func.isRequired,\n handleAddTag: PropTypes.func.isRequired,\n errors: PropTypes.any, // this is either bool or object\n};\n\nexport default withStyles(styles)(EnvironmentFormControl);\n","import React, { Component } from 'react';\nimport PropTypes from 'prop-types';\nimport { compose } from 'redux';\nimport { connect } from 'react-redux';\n\nimport { withStyles } from '@material-ui/core';\nimport DialogTitle from '@material-ui/core/DialogTitle';\nimport Dialog from '@material-ui/core/Dialog';\nimport Button from '@material-ui/core/Button';\nimport Chip from '@material-ui/core/Chip';\nimport List from '@material-ui/core/List';\nimport CircularProgress from '@material-ui/core/CircularProgress';\n\nimport {\n createEnvironment,\n editEnvironment,\n resetEnvironmentsProps,\n} from '../../../actions/environments';\nimport { resetMessageBoardsProps } from '../../../actions/messageboards';\nimport { getUserImages } from '../../../actions/images';\nimport ImagePreview from './ImagePreview';\nimport EnvironmentFormText from './EnvironmentFormText';\nimport EnvironmentFormControl from './EnvironmentFormControl';\nimport { isLoading } from '../../../reducers/display';\nimport types from '../../../utils/types';\n\nconst styles = theme => ({\n container: {\n display: 'flex',\n flexWrap: 'wrap',\n },\n tagStyle: {\n margin: theme.spacing.unit,\n },\n loadingStyle: {\n margin: 'auto',\n padding: '5px',\n },\n});\n\nclass EnvironmentForm extends Component {\n constructor(props) {\n super(props);\n const { envExists, environment, repository } = props;\n this.state = {\n id: envExists ? environment.id : repository.id,\n name: envExists ? environment.name : repository.name,\n description: envExists ? environment.description : repository.description,\n tag: '',\n tags: envExists && Boolean(environment.tags) ? environment.tags : [],\n topic: envExists && Boolean(environment.topic) ? environment.topic.id : '',\n avatar: envExists ? environment.currentAvatar : null,\n avatarId:\n envExists && Boolean(environment.currentAvatar) ? environment.currentAvatar.id : null,\n };\n this.handleSelect = this.handleSelect.bind(this);\n this.handleSubmit = this.handleSubmit.bind(this);\n this.handleChange = this.handleChange.bind(this);\n this.handleAddTag = this.handleAddTag.bind(this);\n this.handleDeleteTag = this.handleDeleteTag.bind(this);\n this.handleChangeTopic = this.handleChangeTopic.bind(this);\n }\n\n handleSelect = selectedAvatar => {\n if (selectedAvatar !== null) {\n this.setState({\n avatar: selectedAvatar,\n avatarId: selectedAvatar.id,\n });\n } else {\n this.setState({\n avatar: selectedAvatar,\n avatarId: null,\n });\n }\n };\n\n handleSubmit = event => {\n event.preventDefault();\n if (this.props.envExists) {\n this.props.editEnvironment(this.state);\n } else {\n const { name, description, id, tags, topic, avatarId } = this.state;\n this.props.createEnvironment(name, description, id, tags, topic, avatarId);\n }\n };\n\n handleChange = name => event => {\n event.preventDefault();\n this.setState({\n [name]: event.target.value,\n });\n };\n\n handleAddTag = () => {\n const tagArray = this.state.tags;\n if (!(tagArray.includes(this.state.tag) || this.state.tag === '')) {\n this.setState({\n tags: tagArray.concat([this.state.tag]),\n tag: '',\n });\n }\n };\n\n handleDeleteTag = tag => event => {\n event.preventDefault();\n const tagArray = this.state.tags;\n tagArray.splice(tagArray.indexOf(tag), 1);\n this.setState({\n tag: '',\n tags: tagArray,\n });\n };\n\n handleChangeTopic = event => {\n this.setState({ [event.target.name]: event.target.value });\n };\n\n componentDidUpdate(prevProps) {\n if (prevProps.uploadSuccess !== this.props.uploadSuccess && this.props.uploadSuccess) {\n // this is called many times in a row because the update is not fast enough\n this.props.onClose();\n this.props.resetEnvironmentsProps();\n }\n if (prevProps.uploadedImage !== this.props.uploadedImage && this.props.uploadedImage) {\n const { uploadedImage } = this.props;\n this.setState({\n avatar: uploadedImage,\n avatarId: uploadedImage.id,\n });\n }\n if (prevProps.uploadMessageBoard !== this.props.uploadMessageBoard && this.props.uploadMessageBoard) {\n // this is called many times in a row because the update is not fast enough\n this.props.resetMessageBoardsProps();\n }\n }\n\n render() {\n const { classes, repository, topics, errors, loading } = this.props;\n const { tags } = this.state;\n return (\n
\n \n \n {this.props.envExists ? 'Edit Environment' : 'Create Environment'}\n \n \n \n \n {tags.length > 0 && (\n \n {tags.map(tag => (\n \n ))}\n \n )}\n {loading ? (\n \n ) : (\n \n )}\n \n
\n );\n }\n}\n\nEnvironmentForm.propTypes = {\n getUserImages: PropTypes.func.isRequired,\n createEnvironment: PropTypes.func.isRequired,\n editEnvironment: PropTypes.func.isRequired,\n resetEnvironmentsProps: PropTypes.func.isRequired,\n resetMessageBoardsProps: PropTypes.func.isRequired,\n onClose: PropTypes.func.isRequired,\n repository: PropTypes.object.isRequired,\n environment: PropTypes.object,\n open: PropTypes.bool.isRequired,\n envExists: PropTypes.bool.isRequired,\n topics: PropTypes.arrayOf(PropTypes.object),\n uploadSuccess: PropTypes.any, // this is either undefined or bool\n uploadedImage: PropTypes.any, // this is either undefined or object\n loading: PropTypes.bool.isRequired,\n errors: PropTypes.oneOfType([\n PropTypes.bool,\n PropTypes.object,\n ]).isRequired,\n};\n\nconst mapStateToProps = state => ({\n topics: state.topics.topics,\n uploadSuccess: state.environments.uploadSuccess,\n uploadedImage: state.images.uploadedImage,\n uploadMessageBoard: state.messageboards.uploadSuccess,\n loading:\n isLoading(state.display, types.CREATE_ENVIRONMENT) ||\n isLoading(state.display, types.EDIT_ENVIRONMENT),\n});\n\nconst mapDispatchToProps = {\n getUserImages,\n createEnvironment,\n editEnvironment,\n resetEnvironmentsProps,\n resetMessageBoardsProps,\n};\n\nexport default compose(\n connect(\n mapStateToProps,\n mapDispatchToProps\n ),\n withStyles(styles)\n)(EnvironmentForm);\n","import React, { Component } from 'react';\nimport PropTypes from 'prop-types';\nimport { connect } from 'react-redux';\nimport { compose } from 'redux';\n\nimport Dialog from '@material-ui/core/Dialog';\nimport DialogTitle from '@material-ui/core/DialogTitle';\nimport DialogActions from '@material-ui/core/DialogActions';\nimport Button from '@material-ui/core/Button';\nimport { withStyles } from '@material-ui/core';\nimport CircularProgress from '@material-ui/core/CircularProgress';\n\nimport { deleteEnvironment, resetEnvironmentsProps } from '../../../actions/environments';\nimport { isLoading } from '../../../reducers/display';\nimport types from '../../../utils/types';\n\nconst styles = theme => ({\n loadingStyle: {\n marginRight: '20px',\n },\n});\n\nclass DeleteEnvironment extends Component {\n constructor(props) {\n super(props);\n this.handleDelete = this.handleDelete.bind(this);\n }\n handleDelete = () => {\n this.props.deleteEnvironment(this.props.environment);\n };\n componentDidUpdate(prevProps) {\n if (prevProps.deleteSuccess !== this.props.deleteSuccess && this.props.deleteSuccess) {\n // this is called many times in a row because the update is not fast enough\n this.props.handleCloseDelete();\n this.props.resetEnvironmentsProps();\n }\n }\n\n render() {\n const { classes, openDelete, handleCloseDelete, environment, loading } = this.props;\n return (\n \n \n Are you sure you want to delete the environment \"{environment.name}\"?\n \n \n {loading ? (\n \n ) : (\n \n )}\n \n \n \n );\n }\n}\n\nDeleteEnvironment.propTypes = {\n handleCloseDelete: PropTypes.func.isRequired,\n environment: PropTypes.object,\n openDelete: PropTypes.bool.isRequired,\n loading: PropTypes.bool.isRequired,\n deleteSuccess: PropTypes.any, // this is either undefined or bool\n};\n\nconst mapStateToProps = state => ({\n deleteSuccess: state.environments.deleteSuccess,\n loading: isLoading(state.display, types.DELETE_ENVIRONMENT),\n});\n\nconst mapDispatchToProps = {\n deleteEnvironment,\n resetEnvironmentsProps,\n};\n\nexport default compose(\n connect(\n mapStateToProps,\n mapDispatchToProps\n ),\n withStyles(styles)\n)(DeleteEnvironment);\n","import React from 'react';\nimport PropTypes from 'prop-types';\n\nimport Fab from '@material-ui/core/Fab';\n\nimport AddIcon from '@material-ui/icons/Add';\nimport Edit from '@material-ui/icons/Edit';\nimport Delete from '@material-ui/icons/Delete';\n\nconst RepositoryItemFormArea = ({ classes, envExists, handleClickDelete, handleClickOpen }) => {\n return (\n
\n {envExists ? (\n
\n \n \n \n \n \n \n
\n ) : (\n \n \n \n )}\n
\n );\n};\n\nRepositoryItemFormArea.propTypes = {\n classes: PropTypes.object.isRequired,\n envExists: PropTypes.bool.isRequired,\n handleClickDelete: PropTypes.func.isRequired,\n handleClickOpen: PropTypes.func.isRequired,\n};\n\nexport default RepositoryItemFormArea;\n","import React, { Component } from 'react';\nimport { connect } from 'react-redux';\nimport { compose } from 'redux';\nimport PropTypes from 'prop-types';\n\nimport { withStyles } from '@material-ui/core';\nimport ListItem from '@material-ui/core/ListItem';\nimport Card from '@material-ui/core/Card';\nimport CardActions from '@material-ui/core/CardActions';\nimport CardContent from '@material-ui/core/CardContent';\nimport Typography from '@material-ui/core/Typography';\nimport Button from '@material-ui/core/Button';\n\nimport { SciGymLogo, GithubIcon } from '../../files/images';\nimport EnvironmentForm from './EnvironmentForm';\nimport DeleteEnvironment from './DeleteEnvironment';\nimport { resetEnvironmentsErrors } from '../../../actions/environments';\nimport RepositoryItemFormArea from './RepositoryItemFormArea';\nimport types from '../../../utils/types';\nimport constants from '../../../utils/constants';\nimport { getErrors } from '../../../reducers/errors';\n\nconst styles = theme => ({\n root: {\n display: 'flex',\n flexFlow: 'row wrap',\n },\n cardStyle: {\n width: '100%',\n },\n logoStyle: {\n flex: '1 1 10px',\n margin: 'auto',\n top: '0',\n bottom: '0',\n textAlign: 'center',\n },\n cardContentStyle: {\n flex: '1 1 400px',\n },\n buttonStyle: {\n margin: theme.spacing.unit,\n },\n buttonPosition: {\n position: 'absolute',\n right: '40px',\n bottom: '25px',\n },\n});\n\nclass RepositoryItem extends Component {\n constructor(props) {\n super(props);\n this.state = {\n open: false,\n openDelete: false, // WHATS WRONG WITHT THIS STATE????\n };\n this.handleClickOpen = this.handleClickOpen.bind(this);\n this.handleClose = this.handleClose.bind(this);\n this.handleClickDelete = this.handleClickDelete.bind(this);\n this.handleCloseDelete = this.handleCloseDelete.bind(this);\n }\n\n get avatarUrl() {\n const isEnvironment = Boolean(this.props.environment);\n if (!isEnvironment) {\n return ;\n }\n \n const avatar = this.props.environment.currentAvatar;\n if (!avatar) {\n return ;\n }\n\n const imageSrc = constants.MEDIA_URL.concat(avatar.filePath);\n return \"\"\n }\n\n handleClickOpen = () => {\n this.setState({\n open: true,\n });\n };\n\n handleClose = () => {\n this.setState({ open: false });\n this.props.resetEnvironmentsErrors();\n };\n\n handleClickDelete = () => {\n this.setState({ openDelete: true });\n };\n\n handleCloseDelete = () => {\n this.setState({ openDelete: false }); // ADD Errors?\n };\n\n render() {\n const { classes, errorsCreate, errorsEdit } = this.props;\n const errors = Boolean(errorsCreate) ? errorsCreate : Boolean(errorsEdit) && errorsEdit;\n const { name, description, owner, htmlUrl } = this.props.repository;\n const { openDelete } = this.state;\n const isEnvironment = Boolean(this.props.environment);\n const keyId = isEnvironment\n ? this.props.environment.id\n : this.props.repository.id;\n\n\n return (\n \n \n
\n
\n {this.avatarUrl}\n
\n
\n \n \n {name}\n \n \n {description}\n \n \n Owner:{' '}\n {owner.username} \n \n \n \n \n \n \n
\n
\n {Boolean(this.props.environment) && (\n \n )}\n \n
\n
\n );\n }\n}\n\nRepositoryItem.propTypes = {\n key: PropTypes.string,\n repository: PropTypes.object.isRequired,\n environment: PropTypes.object,\n errorsCreate: PropTypes.oneOfType([\n PropTypes.bool,\n PropTypes.object,\n ]).isRequired,\n errorsEdit: PropTypes.oneOfType([\n PropTypes.bool,\n PropTypes.object,\n ]).isRequired,\n resetEnvironmentsErrors: PropTypes.func.isRequired,\n};\n\nfunction mapStateToProps(state, ownProps) {\n const repoId = ownProps.repository.id;\n const { environments } = state.environments; // this is empty at reload\n return {\n environment: environments.find(env => env.repository.id === repoId), // check env.repo instead of id\n errorsCreate: getErrors(state.errors, types.CREATE_ENVIRONMENT),\n errorsEdit: getErrors(state.errors, types.EDIT_ENVIRONMENT),\n };\n}\n\nconst mapDispatchToProps = {\n resetEnvironmentsErrors,\n};\n\nexport default compose(\n connect(\n mapStateToProps,\n mapDispatchToProps\n ),\n withStyles(styles)\n)(RepositoryItem);\n","import React from 'react';\nimport PropTypes from 'prop-types';\nimport List from '@material-ui/core/List';\nimport Divider from '@material-ui/core/Divider';\nimport Typography from '@material-ui/core/Typography';\n\nimport RepositoryItem from './RepositoryItem';\n\nconst RepositoryList = ({ classes, emptyGym, gymRepo, notGymRepo }) => {\n return (\n \n \n OpenAI Gym Repositories\n \n {!emptyGym ? (\n gymRepo.map(r => (\n \n \n \n \n ))\n ) : (\n \n \n You don't have any Gym Repositories!\n \n \n \n )}\n \n Other Repositories\n \n {notGymRepo.length === 0 ? (\n \n \n You don't have any other Repositories!\n \n \n \n ) : (\n notGymRepo.map(r => (\n \n \n \n \n ))\n )}\n \n );\n};\n\nRepositoryList.propTypes = {\n classes: PropTypes.object.isRequired,\n emptyGym: PropTypes.bool.isRequired,\n gymRepo: PropTypes.arrayOf(PropTypes.object),\n notGymRepo: PropTypes.arrayOf(PropTypes.object),\n};\n\nexport default RepositoryList;\n","import React, { Component } from 'react';\nimport { connect } from 'react-redux';\nimport { compose } from 'redux';\nimport PropTypes from 'prop-types';\n\nimport { withStyles } from '@material-ui/core';\nimport Grid from '@material-ui/core/Grid';\nimport CircularProgress from '@material-ui/core/CircularProgress';\nimport Button from '@material-ui/core/Button';\nimport Refresh from '@material-ui/icons/Refresh';\nimport Typography from '@material-ui/core/Typography';\n\nimport types from '../../../utils/types';\nimport { isLoading } from '../../../reducers/display';\nimport { findGymRepos, getUserRepositories } from '../../../actions/repositories';\nimport RepositoryList from './RepositoryList';\n\nconst styles = theme => ({\n root: {\n flexGrow: 1,\n },\n buttonStyle: {\n margin: theme.spacing.unit * 2,\n },\n leftIcon: {\n marginRight: theme.spacing.unit,\n },\n title: {\n margin: theme.spacing.unit * 2,\n marginTop: theme.spacing.unit * 6,\n },\n});\n\nclass Repositories extends Component {\n constructor(props) {\n super(props);\n this.findGymRepos = this.findGymRepos.bind(this);\n }\n\n componentDidMount() {\n if (this.props.userExists) {\n this.props.getUserRepositories();\n }\n }\n\n findGymRepos() {\n this.props.findGymRepos();\n }\n\n render() {\n const { repositories, findGymLoading, gymRepo, notGymRepo } = this.props;\n const empty = repositories.length === 0;\n const emptyGym = gymRepo.length === 0;\n const { classes } = this.props;\n return (\n
\n \n
\n \n My repositories\n \n {findGymLoading ? (\n \n ) : (\n \n \n Refresh my repositories\n \n )}\n {empty && (\n \n No repositories found\n \n )}\n {!empty && (\n \n )}\n
\n
\n
\n );\n }\n}\n\nRepositories.propTypes = {\n repositories: PropTypes.arrayOf(PropTypes.object),\n findGymRepos: PropTypes.func.isRequired,\n};\n\nconst mapStateToProps = state => ({\n userExists: state.user.exists,\n repositories: state.repositories.userRepositories,\n findGymLoading: isLoading(state.display, types.FIND_GYM_REPOS),\n gymRepo: state.repositories.userRepositories.filter(repo => repo['gym']),\n notGymRepo: state.repositories.userRepositories.filter(repo => !repo['gym']),\n});\n\nconst mapDispatchToProps = {\n findGymRepos,\n getUserRepositories,\n};\n\nexport default compose(\n connect(\n mapStateToProps,\n mapDispatchToProps\n ),\n withStyles(styles)\n)(Repositories);\n","import React, { Component } from 'react';\nimport { connect } from 'react-redux';\nimport { compose } from 'redux';\nimport PropTypes from 'prop-types';\n\nimport Dialog from '@material-ui/core/Dialog';\nimport DialogTitle from '@material-ui/core/DialogTitle';\nimport DialogActions from '@material-ui/core/DialogActions';\nimport CircularProgress from '@material-ui/core/CircularProgress';\nimport Button from '@material-ui/core/Button';\nimport { withStyles } from '@material-ui/core';\nimport { deleteImage, resetImageProps } from '../../../actions/images';\nimport { isLoading } from '../../../reducers/display';\nimport types from '../../../utils/types';\n\nconst styles = theme => ({\n loadingStyle: {\n marginRight: '20px',\n },\n});\n\nclass DeleteImage extends Component {\n constructor(props) {\n super(props);\n this.handleDelete = this.handleDelete.bind(this);\n }\n handleDelete = () => {\n this.props.deleteImage(this.props.image);\n };\n\n componentDidUpdate(prevProps) {\n if (prevProps.deleteSuccess !== this.props.deleteSuccess && this.props.deleteSuccess) {\n // this is called many times in a row because the update is not fast enough\n this.props.handleCloseDelete();\n this.props.resetImageProps();\n }\n }\n\n render() {\n const { classes, openDelete, handleCloseDelete, loading } = this.props;\n return (\n \n Are you sure you want to delete the image?\n \n {loading ? (\n \n ) : (\n \n )}\n \n \n \n );\n }\n}\n\nDeleteImage.propTypes = {\n handleCloseDelete: PropTypes.func.isRequired,\n image: PropTypes.object.isRequired,\n openDelete: PropTypes.bool.isRequired,\n loading: PropTypes.bool.isRequired,\n deleteSuccess: PropTypes.any, // this is undefined or bool\n};\n\nconst mapStateToProps = state => ({\n deleteSuccess: state.images.deleteSuccess,\n loading: isLoading(state.display, types.DELETE_IMAGE),\n});\nconst mapDispatchToProps = { deleteImage, resetImageProps };\n\nexport default compose(\n connect(\n mapStateToProps,\n mapDispatchToProps\n ),\n withStyles(styles)\n)(DeleteImage);\n","import React, { Component } from 'react';\nimport PropTypes from 'prop-types';\n\nimport Card from '@material-ui/core/Card';\nimport CardMedia from '@material-ui/core/CardMedia';\nimport Fab from '@material-ui/core/Fab';\n\nimport Delete from '@material-ui/icons/Delete';\n\nimport constants from '../../../utils/constants';\nimport DeleteImage from './DeleteImage';\n\nclass ImageCard extends Component {\n constructor(props) {\n super(props);\n this.state = {\n openDelete: false,\n };\n this.handleClickDelete.bind(this);\n this.handleCloseDelete.bind(this);\n }\n handleClickDelete = () => {\n this.setState({ openDelete: true });\n };\n\n handleCloseDelete = () => {\n this.setState({ openDelete: false });\n };\n render() {\n const { classes, image } = this.props;\n const { openDelete } = this.state;\n return (\n \n \n
\n \n \n \n \n
\n \n
\n );\n }\n}\n\nImageCard.propTypes = {\n classes: PropTypes.object.isRequired,\n image: PropTypes.object.isRequired,\n};\n\nexport default ImageCard;\n","import React, { Component } from 'react';\nimport PropTypes from 'prop-types';\nimport { connect } from 'react-redux';\nimport { compose } from 'redux';\n\nimport { withStyles } from '@material-ui/core';\nimport Typography from '@material-ui/core/Typography';\nimport Grid from '@material-ui/core/Grid';\nimport InfoIcon from '@material-ui/icons/InfoOutlined';\n\nimport { getUserImages } from '../../../actions/images';\nimport ImageCard from './ImageCard';\n\nconst styles = theme => ({\n root: {\n ...theme.mixins.gutters(),\n paddingTop: theme.spacing.unit * 2,\n paddingBottom: theme.spacing.unit * 3,\n marginLeft: theme.spacing.unit * 2,\n maxWidth: '600px',\n },\n cardStyle: {\n width: '200px',\n },\n mediaStyle: {\n height: '200px',\n },\n rootGrid: {\n paddingTop: theme.spacing.unit,\n flexGrow: 1,\n },\n buttonStyle: {\n margin: theme.spacing.unit,\n },\n iconStyle: {\n marginRight: theme.spacing.unit,\n }\n});\n\nclass Images extends Component {\n render() {\n const { classes, userImages } = this.props;\n if (userImages.length > 0) {\n return (\n
\n \n My Images\n \n \n You can upload more images when creating environments from your repositories.\n \n \n {userImages.map(image => (\n \n \n \n ))}\n \n
\n );\n } else {\n return (\n
\n \n My Images\n \n \n You don't have any images!\n \n \n Images can be uploaded when you are creating environments from your repositories.\n \n
\n );\n }\n }\n}\n\nImages.propTypes = {\n userImages: PropTypes.arrayOf(PropTypes.object),\n getUserImages: PropTypes.func.isRequired,\n};\n\nconst mapStateToProps = state => ({\n userImages: state.images.userImages,\n});\n\nconst mapDispatchToProps = { getUserImages };\n\nexport default compose(\n connect(\n mapStateToProps,\n mapDispatchToProps\n ),\n withStyles(styles)\n)(Images);\n","import React from 'react'\nimport { withStyles } from '@material-ui/core';\n\nimport Grid from '@material-ui/core/Grid';\nimport ListItem from '@material-ui/core/ListItem';\nimport Typography from '@material-ui/core/Typography';\nimport Hidden from '@material-ui/core/Hidden';\n\nconst styles = theme => ({\n root: {\n flex: '1',\n },\n});\n\nconst MessageBoardsHead = props => {\n const { classes } = props;\n return (\n \n \n \n \n My Message Boards\n \n \n \n \n \n \n \n Tags\n \n \n \n \n \n \n \n Actions\n \n \n \n \n );\n};\n\nexport default withStyles(styles)(MessageBoardsHead);\n","import React from 'react';\nimport PropTypes from 'prop-types';\nimport { withStyles } from '@material-ui/core';\n\nimport TextField from '@material-ui/core/TextField';\n\nconst styles = theme => ({\n root: {\n width: '100%',\n },\n child: {\n width: 'auto',\n margin: theme.spacing.unit * 2,\n },\n textField: {\n width: '100%',\n },\n});\n\nconst MessageBoardFormText = ({ classes, title, description, handleChange, errors }) => {\n return (\n
\n
\n \n
\n
\n \n
\n
\n );\n};\n\nMessageBoardFormText.propTypes = {\n classes: PropTypes.object.isRequired,\n title: PropTypes.string.isRequired,\n description: PropTypes.string,\n handleChange: PropTypes.func.isRequired,\n errors: PropTypes.any, // this is either bool or object\n};\n\nexport default withStyles(styles)(MessageBoardFormText);\n","import React from 'react';\nimport PropTypes from 'prop-types';\nimport { withStyles } from '@material-ui/core';\n\nimport FormControl from '@material-ui/core/FormControl';\nimport FormHelperText from '@material-ui/core/FormHelperText';\nimport Input from '@material-ui/core/Input';\nimport InputLabel from '@material-ui/core/InputLabel';\nimport InputAdornment from '@material-ui/core/InputAdornment';\nimport IconButton from '@material-ui/core/IconButton';\n\nimport AddIcon from '@material-ui/icons/Add';\n\nconst styles = theme => ({\n root: {\n width: '100%',\n },\n child: {\n width: 'auto',\n margin: theme.spacing.unit * 2,\n },\n textField: {\n width: '100%',\n },\n});\n\nconst MessageBoardFormControl = ({\n classes,\n tag,\n handleChange,\n handleAddTag,\n errors,\n}) => {\n return (\n
\n
\n \n Add a Tag\n \n \n \n \n \n }\n error={Boolean(errors && errors.tags)}\n />\n \n {errors && errors.tags}\n \n \n
\n
\n );\n};\n\nMessageBoardFormControl.propTypes = {\n classes: PropTypes.object.isRequired,\n tag: PropTypes.string,\n handleChange: PropTypes.func.isRequired,\n handleAddTag: PropTypes.func.isRequired,\n errors: PropTypes.any, // this is either bool or object\n};\n\nexport default withStyles(styles)(MessageBoardFormControl);\n","import React, { Component } from 'react';\nimport PropTypes from 'prop-types';\nimport { compose } from 'redux';\nimport { connect } from 'react-redux';\n\nimport { withStyles } from '@material-ui/core';\nimport DialogTitle from '@material-ui/core/DialogTitle';\nimport Dialog from '@material-ui/core/Dialog';\nimport Button from '@material-ui/core/Button';\nimport Chip from '@material-ui/core/Chip';\nimport List from '@material-ui/core/List';\nimport CircularProgress from '@material-ui/core/CircularProgress';\n\nimport {\n createMessageBoard,\n resetMessageBoardsProps,\n editMessageBoard,\n} from '../../actions/messageboards';\nimport { getErrors } from '../../reducers/errors';\nimport MessageBoardFormText from './MessageBoardFormText';\nimport MessageBoardFormControl from './MessageBoardFormControl';\nimport { isLoading } from '../../reducers/display';\nimport types from '../../utils/types';\n\nconst styles = theme => ({\n container: {\n display: 'flex',\n flexWrap: 'wrap',\n },\n tagStyle: {\n margin: theme.spacing.unit,\n },\n loadingStyle: {\n margin: 'auto',\n padding: '5px',\n },\n});\n\nclass MessageBoardForm extends Component {\n constructor(props) {\n super(props);\n const { environment, messageboard } = props;\n const envExists = Boolean(environment);\n const boardExists = Boolean(messageboard)\n this.state = {\n id: boardExists ? messageboard.id : undefined,\n environment: envExists ? environment.id : undefined,\n title: boardExists ? messageboard.title : '',\n description: boardExists ? messageboard.description : '',\n tag: '',\n tags: boardExists && Boolean(messageboard.tags) ? messageboard.tags : [],\n };\n this.handleSubmit = this.handleSubmit.bind(this);\n this.handleChange = this.handleChange.bind(this);\n this.handleAddTag = this.handleAddTag.bind(this);\n this.handleDeleteTag = this.handleDeleteTag.bind(this);\n }\n\n handleSubmit = event => {\n event.preventDefault();\n const boardExists = Boolean(this.props.messageboard);\n if (boardExists) {\n this.props.editMessageBoard(this.state);\n } else {\n this.props.createMessageBoard(this.state);\n }\n }\n\n handleChange = title => event => {\n event.preventDefault();\n this.setState({\n [title]: event.target.value,\n });\n };\n\n handleAddTag = () => {\n const tagArray = this.state.tags;\n if (!(tagArray.includes(this.state.tag) || this.state.tag === '')) {\n this.setState({\n tags: tagArray.concat([this.state.tag]),\n tag: '',\n });\n }\n };\n\n handleDeleteTag = tag => event => {\n event.preventDefault();\n const tagArray = this.state.tags;\n tagArray.splice(tagArray.indexOf(tag), 1);\n this.setState({\n tag: '',\n tags: tagArray,\n });\n };\n\n componentDidUpdate(prevProps) {\n if (prevProps.uploadSuccess !== this.props.uploadSuccess && this.props.uploadSuccess) {\n // this is called many times in a row because the update is not fast enough\n this.props.onClose();\n this.props.resetMessageBoardsProps();\n }\n if (this.state.environment === undefined && Boolean(this.props.environment)) {\n this.setState({ environment: this.props.environment.id })\n }\n }\n\n render() {\n const { classes, errors, loading } = this.props;\n const { tags } = this.state;\n return (\n
\n \n \n Start a Discussion\n \n \n \n {tags.length > 0 && (\n \n {tags.map(tag => (\n \n ))}\n \n )}\n {loading ? (\n \n ) : (\n \n )}\n \n
\n );\n }\n}\n\nMessageBoardForm.propTypes = {\n createMessageBoard: PropTypes.func.isRequired,\n resetMessageBoardsProps: PropTypes.func.isRequired,\n editMessageBoard: PropTypes.func.isRequired,\n onClose: PropTypes.func.isRequired,\n environment: PropTypes.object.isRequired,\n open: PropTypes.bool.isRequired,\n uploadSuccess: PropTypes.any, // this is either undefined or bool\n loading: PropTypes.bool.isRequired,\n messageboard: PropTypes.any, // this is either undefined or an object\n errors: PropTypes.oneOfType([\n PropTypes.bool,\n PropTypes.object,\n ]).isRequired,\n};\n\nfunction mapStateToProps(state) {\n const errorsCreate = getErrors(state.errors, types.CREATE_MESSAGEBOARD);\n const errorsEdit = getErrors(state.errors, types.EDIT_MESSAGEBOARD);\n const errors = Boolean(errorsCreate) ? errorsCreate : Boolean(errorsEdit) && errorsEdit;\n return {\n uploadSuccess: state.messageboards.uploadSuccess,\n loading:\n isLoading(state.display, types.CREATE_MESSAGEBOARD) ||\n isLoading(state.display, types.EDIT_MESSAGEBOARD),\n errors: errors,\n };\n};\n\nconst mapDispatchToProps = {\n createMessageBoard,\n resetMessageBoardsProps,\n editMessageBoard,\n};\n\nexport default compose(\n connect(\n mapStateToProps,\n mapDispatchToProps\n ),\n withStyles(styles)\n)(MessageBoardForm);\n","import React, { Component } from 'react';\nimport PropTypes from 'prop-types';\nimport { connect } from 'react-redux';\nimport { compose } from 'redux';\n\nimport Dialog from '@material-ui/core/Dialog';\nimport DialogTitle from '@material-ui/core/DialogTitle';\nimport DialogActions from '@material-ui/core/DialogActions';\nimport Button from '@material-ui/core/Button';\nimport { withStyles } from '@material-ui/core';\nimport CircularProgress from '@material-ui/core/CircularProgress';\n\nimport {\n deleteMessageBoard,\n resetMessageBoardsProps,\n} from '../../../actions/messageboards';\nimport { isLoading } from '../../../reducers/display';\nimport types from '../../../utils/types';\n\nconst styles = theme => ({\n loadingStyle: {\n marginRight: '20px',\n },\n});\n\nclass MessageBoardsDelete extends Component {\n constructor(props) {\n super(props);\n this.handleDelete = this.handleDelete.bind(this);\n }\n\n handleDelete = () => {\n this.props.deleteMessageBoard(this.props.messageboard);\n };\n\n componentDidUpdate(prevProps) {\n if (prevProps.deleteSuccess !== this.props.deleteSuccess && this.props.deleteSuccess) {\n // this is called many times in a row because the update is not fast enough\n this.props.handleCloseDelete();\n this.props.resetMessageBoardsProps();\n }\n }\n\n render() {\n const { classes, openDelete, handleCloseDelete, loading } = this.props;\n return (\n \n \n Are you sure you want to delete this message board with all its comments?\n \n \n {loading ? (\n \n ) : (\n \n )}\n \n \n \n );\n }\n}\n\nMessageBoardsDelete.propTypes = {\n handleCloseDelete: PropTypes.func.isRequired,\n messageboard: PropTypes.object,\n openDelete: PropTypes.bool.isRequired,\n loading: PropTypes.bool.isRequired,\n deleteSuccess: PropTypes.any, // this is either undefined or bool\n};\n\nconst mapStateToProps = state => ({\n deleteSuccess: state.messageboards.deleteSuccess,\n loading: isLoading(state.display, types.DELETE_MESSAGEBOARD),\n});\n\nconst mapDispatchToProps = {\n deleteMessageBoard,\n resetMessageBoardsProps,\n};\n\nexport default compose(\n connect(\n mapStateToProps,\n mapDispatchToProps\n ),\n withStyles(styles)\n)(MessageBoardsDelete);\n","import React, { Component } from 'react';\nimport PropTypes from 'prop-types';\n\nimport Fab from '@material-ui/core/Fab';\n\nimport Edit from '@material-ui/icons/Edit';\nimport Delete from '@material-ui/icons/Delete';\n\nimport MessageBoardForm from '../../environment/MessageBoardForm';\nimport MessageBoardsDelete from './MessageBoardsDelete';\n\nclass MessageBoardsActions extends Component {\n constructor(props) {\n super(props);\n this.state = {\n openForm: false,\n openDelete: false\n };\n }\n\n handleClickOpen = () => {\n const newStateOpen = !this.state.openForm;\n this.setState({\n openForm: newStateOpen,\n });\n };\n\n handleClickDelete = () => {\n const newStateOpen = !this.state.openDelete;\n this.setState({\n openDelete: newStateOpen,\n });\n };\n\n handleClose = () => {\n this.setState({ openForm: false, openDelete: false });\n };\n\n render() {\n const { messageboard } = this.props;\n const { classes } = this.props;\n const { openForm, openDelete } = this.state;\n return (\n
\n
\n \n \n \n \n \n \n
\n \n \n
\n );\n }\n};\n\nMessageBoardsActions.propTypes = {\n classes: PropTypes.object.isRequired,\n messageboard: PropTypes.object.isRequired,\n};\n\nexport default MessageBoardsActions;\n","import React, { Component } from 'react'\nimport { withStyles } from '@material-ui/core';\nimport PropTypes from 'prop-types';\nimport { Link } from 'react-router-dom';\n\nimport Grid from '@material-ui/core/Grid';\nimport ListItem from '@material-ui/core/ListItem';\nimport ListItemText from '@material-ui/core/ListItemText';\nimport Avatar from '@material-ui/core/Avatar';\nimport ChatBubbleIcon from '@material-ui/icons/ChatBubble';\nimport Hidden from '@material-ui/core/Hidden';\nimport List from '@material-ui/core/List';\nimport Chip from '@material-ui/core/Chip';\n\nimport MessageBoardsActions from './MessageBoardsActions';\n\nconst styles = theme => ({\n root: {\n flex: '1',\n },\n tagStyle: {\n margin: theme.spacing.unit * 0.2,\n [theme.breakpoints.down('xs')]: {\n margin: '0',\n },\n },\n buttonStyle: {\n margin: theme.spacing.unit * 0.2,\n [theme.breakpoints.down('xs')]: {\n margin: theme.spacing.unit * 0.1,\n },\n }\n});\n\nclass MessageBoardItem extends Component {\n render() {\n const { classes, messageboard } = this.props;\n const { tags, titleUrl, environment } = messageboard\n return (\n \n \n \n \n \n \n \n \n \n \n \n \n \n {tags.map(tag => {\n const label = tag.length > 20 ? tag.substring(0, 17) + '...' : tag // TODO: Do this at breakpoints\n return (\n \n );\n })}\n \n \n \n \n \n \n \n \n \n \n );\n }\n};\n\nMessageBoardItem.propTypes = {\n messageboard: PropTypes.object.isRequired,\n};\n\nexport default withStyles(styles)(MessageBoardItem);\n","import React, { Component } from 'react';\nimport PropTypes from 'prop-types';\nimport { connect } from 'react-redux';\nimport { compose } from 'redux';\n\nimport { withStyles } from '@material-ui/core/styles';\nimport Typography from '@material-ui/core/Typography';\nimport Card from '@material-ui/core/Card';\nimport Divider from '@material-ui/core/Divider';\nimport List from '@material-ui/core/List';\n\nimport MessageBoardsHead from './MessageBoardsHead';\nimport MessageBoardsItem from './MessageBoardsItem';\n\n\nconst styles = theme => ({\n root: {\n ...theme.mixins.gutters(),\n paddingTop: theme.spacing.unit * 2,\n paddingBottom: theme.spacing.unit * 3,\n marginLeft: theme.spacing.unit * 2,\n width: '100%',\n },\n});\n\nclass MessageBoards extends Component {\n render() {\n const { classes, messageboards } = this.props;\n return (\n
\n {messageboards.length > 0 ? (\n \n \n \n \n {messageboards.map(board => (\n \n \n \n \n )\n )}\n \n \n ) : (\n
\n \n My Message Boards\n \n \n You haven't openend any discussions yet!\n \n
\n )\n }\n
\n );\n };\n};\n\nMessageBoards.propTypes = {\n classes: PropTypes.object.isRequired,\n messageboards: PropTypes.arrayOf(PropTypes.object),\n replies: PropTypes.object,\n};\n\nfunction mapStateToProps(state) {\n const { messageboards, num_comments } = state.messageboards;\n const { id } = state.user;\n return {\n messageboards: messageboards.filter(board => board.author.id === id),\n replies: num_comments,\n };\n}\n\nexport default compose(\n connect(mapStateToProps),\n withStyles(styles)\n)(MessageBoards);\n","import React from 'react';\nimport PropTypes from 'prop-types';\nimport { connect } from 'react-redux';\nimport { compose } from 'redux';\nimport { Switch, Route, Redirect } from 'react-router-dom';\n\nimport { withStyles } from '@material-ui/core';\n\nimport Account from './account/Account';\nimport Settings from './Settings';\nimport Repositories from './repositories/Repositories';\nimport Images from './images/Images';\nimport MessageBoards from './messageboards/MessageBoards';\n\nconst styles = theme => ({\n root: {\n display: 'flex',\n flexFlow: 'row nowrap',\n height: '100%',\n },\n});\n\nconst Profile = ({ classes, userExists }) => {\n if (!userExists) {\n return ; // TODO: this probably also redirects after login\n }\n return (\n
\n \n \n \n \n \n \n \n
\n );\n};\n\nProfile.propTypes = {\n userExists: PropTypes.bool.isRequired,\n};\n\nconst mapStateToProps = state => ({\n userExists: state.user.exists,\n});\n\nexport default compose(\n connect(mapStateToProps),\n withStyles(styles)\n)(Profile);\n","// file copied and modified from https://github.com/mui-org/material-ui/blob/master/docs/src/pages/getting-started/page-layout-examples/blog/Markdown.js\nimport React from 'react';\nimport ReactMarkdown from 'markdown-to-jsx';\nimport { withStyles } from '@material-ui/core/styles';\nimport Typography from '@material-ui/core/Typography';\n\nconst styles = theme => ({\n listItem: {\n marginTop: theme.spacing.unit,\n },\n blockquoteStyle: {\n margin: theme.spacing.unit,\n borderLeft: '2px solid #ccc',\n paddingLeft: theme.spacing.unit,\n },\n});\n\nconst options = {\n overrides: {\n pre: {\n props: {\n style: {\n backgroundColor: '#f5f5f5',\n padding: '10px',\n overflowX: 'auto',\n },\n },\n },\n code: {\n props: {\n style: {\n backgroundColor: '#f5f5f5',\n padding: '3px',\n },\n },\n },\n h1: { component: props => },\n h2: { component: props => },\n h3: { component: props => },\n h4: { component: props => },\n p: { component: props => },\n a: { component: props => {props.children} },\n blockquote: {\n component: withStyles(styles)(({ classes, ...props }) => (\n
\n {props.children}\n
\n )),\n },\n li: {\n component: withStyles(styles)(({ classes, ...props }) => (\n
  • \n \n
  • \n )),\n },\n },\n};\n\nfunction Markdown(props) {\n return (\n \n )\n}\n\nexport default Markdown;\n","import React, { Component } from 'react';\nimport PropTypes from 'prop-types';\n\nimport { withStyles } from '@material-ui/core';\nimport Typography from '@material-ui/core/Typography';\nimport Card from '@material-ui/core/Card';\nimport CardMedia from '@material-ui/core/CardMedia';\n\nimport constants from '../../utils/constants';\nimport Markdown from '../Markdown';\n\nconst styles = theme => ({\n root: {\n margin: 'auto',\n paddingLeft: '10%',\n paddingRight: '10%',\n [theme.breakpoints.down('sm')]: {\n paddingLeft: '0%',\n paddingRight: '0%',\n },\n [theme.breakpoints.up('lg')]: {\n paddingLeft: '10%',\n paddingRight: '10%',\n },\n },\n card: {\n maxWidth: '100%',\n marginLeft: theme.spacing.unit * 10,\n marginRight: theme.spacing.unit * 10,\n [theme.breakpoints.down('xs')]: {\n margin: theme.spacing.unit * 1,\n },\n [theme.breakpoints.up('lg')]: {\n marginLeft: theme.spacing.unit * 15,\n marginRight: theme.spacing.unit * 15,\n },\n },\n media: {\n height: 0,\n paddingTop: '75%',\n },\n title: {\n margin: theme.spacing.unit * 2,\n marginTop: theme.spacing.unit * 6,\n },\n markdown: {\n padding: `${theme.spacing.unit * 3}px 0`,\n },\n});\n\nclass Play extends Component {\n state = {\n markdown: '',\n };\n componentDidMount() {\n const mdPath = require('./play.md');\n\n fetch(mdPath)\n .then(response => {\n return response.text();\n })\n .then(text => {\n this.setState({\n markdown: text,\n });\n });\n }\n render() {\n const { markdown } = this.state;\n const { classes } = this.props;\n return (\n
    \n \n How to use SciGym?\n \n \n \n \n \n {markdown}\n \n
    \n );\n }\n}\n\nPlay.propTypes = {\n classes: PropTypes.object.isRequired,\n};\n\nexport default withStyles(styles)(Play);\n","import React, { Component } from 'react';\nimport PropTypes from 'prop-types';\n\nimport { withStyles } from '@material-ui/core';\nimport Typography from '@material-ui/core/Typography';\nimport Card from '@material-ui/core/Card';\nimport CardMedia from '@material-ui/core/CardMedia';\n\nimport constants from '../../utils/constants';\nimport Markdown from '../Markdown';\n\nconst styles = theme => ({\n root: {\n margin: 'auto',\n paddingLeft: '10%',\n paddingRight: '10%',\n [theme.breakpoints.down('sm')]: {\n paddingLeft: '0%',\n paddingRight: '0%',\n },\n [theme.breakpoints.up('lg')]: {\n paddingLeft: '10%',\n paddingRight: '10%',\n },\n },\n card: {\n maxWidth: '100%',\n marginLeft: theme.spacing.unit * 10,\n marginRight: theme.spacing.unit * 10,\n [theme.breakpoints.down('xs')]: {\n margin: theme.spacing.unit * 1,\n },\n [theme.breakpoints.up('lg')]: {\n marginLeft: theme.spacing.unit * 15,\n marginRight: theme.spacing.unit * 15,\n },\n },\n media: {\n height: 0,\n paddingTop: '75%',\n },\n title: {\n margin: theme.spacing.unit * 2,\n marginTop: theme.spacing.unit * 6,\n },\n markdown: {\n padding: `${theme.spacing.unit * 3}px 0`,\n },\n});\n\nclass Contribute extends Component {\n state = {\n markdown: '',\n };\n componentDidMount() {\n const mdPath = require('./contribute.md');\n\n fetch(mdPath)\n .then(response => {\n return response.text();\n })\n .then(text => {\n this.setState({\n markdown: text,\n });\n });\n }\n render() {\n const { markdown } = this.state;\n const { classes } = this.props;\n return (\n
    \n \n How to contribute to SciGym?\n \n \n \n \n \n {markdown}\n \n
    \n );\n }\n}\n\nContribute.propTypes = {\n classes: PropTypes.object.isRequired,\n};\n\nexport default withStyles(styles)(Contribute);\n","import React, { Component } from 'react';\nimport PropTypes from 'prop-types';\nimport YouTube from 'react-youtube';\n\nimport { withStyles } from '@material-ui/core';\nimport Paper from '@material-ui/core/Paper';\nimport Typography from '@material-ui/core/Typography';\nimport AppBar from '@material-ui/core/AppBar';\nimport Tabs from '@material-ui/core/Tabs';\nimport Tab from '@material-ui/core/Tab';\n\nimport Play from './Play';\nimport Contribute from './Contribute';\n\nconst styles = theme => ({\n root: {\n margin: 'auto',\n paddingLeft: '10%',\n paddingRight: '10%',\n backgroundColor: 'AliceBlue',\n paddingTop: theme.spacing.unit,\n [theme.breakpoints.down('sm')]: {\n paddingLeft: '0%',\n paddingRight: '0%',\n },\n [theme.breakpoints.up('lg')]: {\n paddingLeft: '20%',\n paddingRight: '20%',\n },\n },\n playerPaper: {\n display: 'flex',\n width: '100%',\n },\n playerWrapper: {\n position: 'relative',\n width: '100%',\n paddingBottom: '56.25%' /* Player ratio: 16:9 */,\n margin: theme.spacing.unit,\n },\n player: {\n position: 'absolute',\n top: '0',\n left: '0',\n },\n title: {\n position: 'relative',\n margin: theme.spacing.unit * 2,\n marginTop: theme.spacing.unit * 6,\n },\n startPaper: {\n display: 'flexGrow',\n width: '100%',\n marginTop: '10px',\n },\n appBarStyle: {\n backgroundColor: '#039be5', //'#2196f3',//'#448aff',\n zIndex: '0',\n },\n});\n\nfunction TabContainer(props) {\n return (\n \n {props.children}\n \n );\n}\n\nTabContainer.propTypes = {\n children: PropTypes.node.isRequired,\n};\n\nclass GetStarted extends Component {\n state = {\n value: 0,\n };\n\n _onReady(event) {\n // access to player in all event handlers via event.target\n event.target.pauseVideo();\n }\n\n handleChange = (event, value) => {\n this.setState({ value });\n };\n\n render() {\n const vidOpts = {\n height: '100%',\n width: '100%',\n playerVars: {\n // https://developers.google.com/youtube/player_parameters\n autoplay: 0,\n },\n };\n const { classes } = this.props;\n const { value } = this.state;\n return (\n
    \n \n What is SciGym?\n \n \n
    \n \n
    \n
    \n \n \n \n \n \n \n \n {value === 0 && (\n \n \n \n )}\n {value === 1 && (\n \n \n \n )}\n \n
    \n );\n }\n}\n\nGetStarted.propTypes = {\n classes: PropTypes.object.isRequired,\n};\n\nexport default withStyles(styles)(GetStarted);\n","import React from 'react';\nimport PropTypes from 'prop-types';\nimport { withStyles } from '@material-ui/core/styles';\nimport Card from '@material-ui/core/Card';\nimport CardActionArea from '@material-ui/core/CardActionArea';\nimport CardContent from '@material-ui/core/CardContent';\nimport CardMedia from '@material-ui/core/CardMedia';\nimport Typography from '@material-ui/core/Typography';\n\nconst styles = {\n card: {\n width: 200,\n },\n media: {\n height: 200,\n },\n};\n\nfunction ContributorCard(props) {\n const { classes, contributor } = props;\n\n return (\n \n \n \n \n \n {contributor.login}\n \n \n \n \n );\n}\n\nContributorCard.propTypes = {\n classes: PropTypes.object.isRequired,\n contributor: PropTypes.object.isRequired,\n};\n\nexport default withStyles(styles)(ContributorCard);\n","import React from 'react';\nimport PropTypes from 'prop-types';\nimport { connect } from 'react-redux';\nimport { compose } from 'redux';\n\nimport { withStyles } from '@material-ui/core';\nimport Typography from '@material-ui/core/Typography';\nimport Grid from '@material-ui/core/Grid';\n\nimport ContributorCard from './ContributorCard';\nimport constants from '../../utils/constants';\n\nconst styles = theme => ({\n root: {\n margin: 'auto',\n paddingLeft: '10%',\n paddingRight: '10%',\n backgroundColor: 'AliceBlue',\n paddingTop: theme.spacing.unit,\n [theme.breakpoints.down('sm')]: {\n paddingLeft: '0%',\n paddingRight: '0%',\n },\n [theme.breakpoints.up('lg')]: {\n paddingLeft: '20%',\n paddingRight: '20%',\n },\n },\n title: {\n position: 'relative',\n margin: theme.spacing.unit * 2,\n marginTop: theme.spacing.unit * 4,\n },\n gridStyle: {\n overflow: 'auto',\n overflowX: 'scroll',\n flexWrap: 'nowrap',\n // Promote the list into his own layer on Chrome. This cost memory but helps keeping high FPS.\n transform: 'translateZ(0)',\n },\n paragraph: {\n position: 'relative',\n margin: theme.spacing.unit * 2,\n },\n});\n\nfunction Impressum(props) {\n const { classes, contributors } = props;\n return (\n
    \n \n Impressum\n \n \n Authors\n \n \n Hendrik Poulsen Nautrup
    \n Erik Niehaus
    \n Petru Tighineanu
    \n Ryan Sweke
    \n
    \n \n Contact Us\n \n \n If you have any questions or ideas, contact us at info@scigym.ai, or find us on{' '}\n Twitter\n \n \n Supporters\n \n \n \n \n \n \n \n \n \"FWF\"\n \n \n \n Contributors\n \n \n {contributors.map(contributor => (\n \n \n \n ))}\n \n \n Thanks to everyone who contributed!\n \n
    \n );\n}\n\nImpressum.propTypes = {\n classes: PropTypes.object.isRequired,\n contributors: PropTypes.arrayOf(PropTypes.object),\n};\n\nconst mapStateToProps = state => ({\n contributors: state.contributors.contributors,\n});\n\nexport default compose(\n connect(mapStateToProps),\n withStyles(styles)\n)(Impressum);\n","import React, { useState } from 'react';\nimport { connect } from 'react-redux';\nimport PropTypes from 'prop-types';\nimport { compose } from 'redux';\n\nimport { withStyles } from '@material-ui/core/styles';\nimport Snackbar from '@material-ui/core/Snackbar';\nimport IconButton from '@material-ui/core/IconButton';\nimport CloseIcon from '@material-ui/icons/Close';\n\nconst styles = theme => ({\n close: {\n padding: theme.spacing.unit / 2,\n },\n});\n\nconst Notification = ({ classes, notification }) => {\n const [visible, setVisible] = useState(true);\n const handleClose = (_, reason) => {\n if (reason === 'clickaway') return;\n setVisible(false);\n };\n if (!notification) return null;\n return (\n {notification.message}}\n action={[\n \n \n ,\n ]}\n />\n );\n};\nconst StyledNotification = withStyles(styles)(Notification);\n\nconst Notifications = ({ notifications }) => {\n return notifications.map(notification => (\n \n ));\n};\n\nNotifications.propTypes = {\n classes: PropTypes.object.isRequired,\n};\n\nconst mapStateToProps = state => ({\n notifications: state.display.notifications,\n});\nexport default compose(\n connect(mapStateToProps),\n withStyles(styles)\n)(Notifications);\n","import React, { Component } from 'react';\nimport PropTypes from 'prop-types';\n\nimport { withStyles } from '@material-ui/core';\nimport Typography from '@material-ui/core/Typography';\nimport Chip from '@material-ui/core/Chip';\nimport List from '@material-ui/core/List';\nimport Button from '@material-ui/core/Button';\nimport Grid from '@material-ui/core/Grid';\nimport Divider from '@material-ui/core/Divider';\n\nimport LocalOffer from '@material-ui/icons/LocalOffer';\n\nimport { GithubIcon } from '../files/images';\nimport VerificationChip from '../VerificationChip';\n\nconst styles = theme => ({\n root: {\n marginTop: theme.spacing.unit * 4,\n },\n contentItems: {\n marginLeft: '20px',\n },\n tagStyle: {\n margin: theme.spacing.unit,\n [theme.breakpoints.down('xs')]: {\n margin: '3px',\n },\n },\n buttonStyle: {\n margin: theme.spacing.unit,\n [theme.breakpoints.down('xs')]: {\n margin: '0',\n },\n },\n chipPosition: {\n position: 'relative',\n top: '20%',\n },\n dividerStyle: {\n marginBottom: theme.spacing.unit,\n },\n});\n\nclass EnvironmentDetailContent extends Component {\n render() {\n const { classes } = this.props;\n const { owner, htmlUrl, gym, fork } = this.props.environment.repository;\n const { name, description, scigym, tags, topic } = this.props.environment;\n return (\n
    \n \n {name}\n \n \n
    \n \n {description}\n \n \n Owner: {owner.username} {' '}\n {fork ? (forked) : ''}\n \n \n Category: {topic ? {topic.name} : None }\n \n \n \n \n Tags: {tags.length === 0 && None }\n \n \n \n \n {tags.map(tag => (\n }\n label={tag}\n key={tag}\n clickable\n className={classes.tagStyle}\n color=\"primary\"\n variant=\"outlined\"\n />\n ))}\n \n \n \n \n \n \n \n \n \n \n \n
    \n
    \n );\n }\n}\n\nEnvironmentDetailContent.propTypes = {\n environment: PropTypes.object,\n};\n\nexport default withStyles(styles)(EnvironmentDetailContent);\n","import React from 'react'\nimport { withStyles } from '@material-ui/core';\n\nimport Grid from '@material-ui/core/Grid';\nimport ListItem from '@material-ui/core/ListItem';\nimport Typography from '@material-ui/core/Typography';\nimport Hidden from '@material-ui/core/Hidden';\n\nconst styles = theme => ({\n root: {\n flex: '1',\n },\n});\n\nconst MessageBoardHead = props => {\n const { classes } = props;\n return (\n \n \n \n \n Message Board\n \n \n \n \n \n \n \n Tags\n \n \n \n \n \n \n \n Replies\n \n \n \n \n );\n};\n\nexport default withStyles(styles)(MessageBoardHead);\n","import React, { Component } from 'react'\nimport { withStyles } from '@material-ui/core';\nimport PropTypes from 'prop-types';\nimport { Link } from 'react-router-dom';\n\nimport Grid from '@material-ui/core/Grid';\nimport ListItem from '@material-ui/core/ListItem';\nimport ListItemText from '@material-ui/core/ListItemText';\nimport Avatar from '@material-ui/core/Avatar';\nimport ChatBubbleIcon from '@material-ui/icons/ChatBubble';\nimport Typography from '@material-ui/core/Typography';\nimport Hidden from '@material-ui/core/Hidden';\nimport List from '@material-ui/core/List';\nimport Chip from '@material-ui/core/Chip';\n\nconst styles = theme => ({\n root: {\n flex: '1',\n },\n tagStyle: {\n margin: theme.spacing.unit * 0.2,\n [theme.breakpoints.down('xs')]: {\n margin: '0',\n },\n },\n});\n\nclass MessageBoardItem extends Component {\n render() {\n const { classes, messageboard, replies } = this.props;\n const { tags, titleUrl } = messageboard\n return (\n \n \n \n \n \n \n \n \n \n \n \n \n \n {tags.map(tag => {\n const label = tag.length > 20 ? tag.substring(0, 17) + '...' : tag // TODO: Do this at breakpoints\n return (\n \n );\n })}\n \n \n \n \n \n \n \n {replies}\n \n \n \n \n );\n }\n};\n\nMessageBoardItem.propTypes = {\n messageboard: PropTypes.object.isRequired,\n replies: PropTypes.number,\n};\n\nexport default withStyles(styles)(MessageBoardItem);\n","import React, { Component } from 'react';\nimport { connect } from 'react-redux';\nimport { compose } from 'redux';\nimport PropTypes from 'prop-types';\n\nimport { withStyles, Button } from '@material-ui/core';\nimport Card from '@material-ui/core/Card';\nimport Divider from '@material-ui/core/Divider';\nimport Edit from '@material-ui/icons/Edit';\nimport List from '@material-ui/core/List';\nimport MessageBoardHead from './MessageBoardHead';\nimport MessageBoardForm from './MessageBoardForm';\nimport LoginForm from '../auth/LoginForm';\nimport MessageBoardItem from './MessageBoardItem';\nimport ExpandMoreLess from '../ExpandMoreLess';\n\nconst modDisplay = 10;\n\nconst styles = theme => ({\n\n uploadButtonStyle: {\n left: '0px',\n marginBottom: theme.spacing.unit * 4,\n },\n buttonStyle: {\n left: '40%',\n },\n buttonPos: {\n minWidth: '770px',\n [theme.breakpoints.down('xs')]: {\n minWidth: '0px',\n },\n [theme.breakpoints.up('md')]: {\n minWidth: '950px',\n },\n },\n iconStyle: {\n marginLeft: theme.spacing.unit,\n },\n root: {\n marginTop: '40px',\n margin: 'auto',\n width: '90%',\n [theme.breakpoints.up('md')]: {\n width: '75%',\n },\n paddingBottom: '50px',\n },\n});\n\nclass MessageBoard extends Component {\n constructor(props) {\n super(props);\n this.state = {\n openLogin: false,\n openForm: false,\n visibleBoardsCount: modDisplay,\n };\n }\n\n handleExpandMore = () => {\n this.setState({ visibleBoardsCount: this.state.visibleBoardsCount + modDisplay });\n };\n\n handleExpandLess = () => {\n this.setState({ visibleBoardsCount: this.state.visibleBoardsCount - modDisplay });\n };\n\n handleClickOpenLogin = () => {\n const newStateOpen = !this.state.openLogin;\n this.setState({\n openLogin: newStateOpen,\n });\n };\n\n handleClickOpenForm = () => {\n const newStateOpen = !this.state.openForm;\n this.setState({\n openForm: newStateOpen,\n });\n };\n\n handleCloseLogin = () => {\n this.setState({ openLogin: false });\n };\n\n handleCloseForm = () => {\n this.setState({ openForm: false });\n };\n\n render() {\n const { classes, environment, userExists, messageboards, replies } = this.props;\n const { openLogin, openForm } = this.state;\n const callbackURL = Boolean(environment) ? 'env/'.concat(environment.name) : ''\n const all = true ? this.state.visibleBoardsCount >= messageboards.length : false;\n const none = true ? this.state.visibleBoardsCount <= modDisplay : false;\n const visibleBoards = messageboards.slice(0, this.state.visibleBoardsCount);\n return (\n
    \n \n \n \n \n \n {visibleBoards.map(board => (\n \n \n \n \n )\n )}\n \n \n \n \n \n
    \n );\n }\n}\n\nMessageBoard.propTypes = {\n environment: PropTypes.object,\n messageboards: PropTypes.arrayOf(PropTypes.object),\n replies: PropTypes.object,\n env_url: PropTypes.string.isRequired,\n userExists: PropTypes.bool.isRequired,\n};\n\nfunction mapStateToProps(state, ownProps) {\n const { messageboards, num_comments } = state.messageboards;\n const { env_url } = ownProps;\n return {\n messageboards: messageboards.filter(board => board.environment.url === env_url),\n userExists: state.user.exists,\n replies: num_comments\n };\n}\n\nexport default compose(\n connect(mapStateToProps),\n withStyles(styles)\n)(MessageBoard);\n","import React, { Component } from 'react';\nimport { connect } from 'react-redux';\nimport { compose } from 'redux';\nimport PropTypes from 'prop-types';\n\nimport { withStyles } from '@material-ui/core';\nimport Typography from '@material-ui/core/Typography';\nimport Grid from '@material-ui/core/Grid';\nimport Card from '@material-ui/core/Card';\nimport CardMedia from '@material-ui/core/CardMedia';\n\nimport constants from '../../utils/constants';\nimport EnvironmentDetailContent from './EnvironmentDetailContent';\nimport MessageBoard from './MessageBoard';\n\nconst styles = theme => ({\n root: {\n display: 'flex',\n flexFlow: 'row wrap',\n backgroundColor: 'AliceBlue',\n },\n title: {\n margin: theme.spacing.unit * 2,\n marginTop: theme.spacing.unit * 6,\n },\n gridStyle: {\n paddingTop: '50px',\n },\n gridImg: {\n width: '300px',\n margin: theme.spacing.unit,\n [theme.breakpoints.down('sm')]: {\n width: '200px',\n },\n },\n gridContent: {\n width: '600px',\n margin: theme.spacing.unit,\n [theme.breakpoints.down('md')]: {\n width: '400px',\n },\n },\n imgCardStyle: {\n width: '175px',\n [theme.breakpoints.down('sm')]: {\n width: '150px',\n },\n },\n mediaStyle: {\n height: '175px',\n [theme.breakpoints.down('sm')]: {\n height: '150px',\n },\n },\n});\n\nclass EnvironmentDetail extends Component {\n\n render() {\n const { classes, environment } = this.props;\n const { env_url } = this.props.match.params\n let filePath = constants.STATIC_URL + constants.SCIGYM_LOGO;\n if (!(environment === undefined)) {\n const { currentAvatar } = this.props.environment;\n if (currentAvatar != null) {\n filePath = constants.MEDIA_URL.concat(currentAvatar.filePath)\n }\n }\n return (\n
    \n {environment === undefined ? (\n \n There is no environment here...\n \n ) : (\n
    \n \n \n \n \n \n \n \n \n \n \n \n
    \n )}\n
    \n );\n }\n}\n\nEnvironmentDetail.propTypes = {\n environment: PropTypes.object,\n};\n\nfunction mapStateToProps(state, ownProps) {\n const { environments } = state.environments;\n let { env_url } = ownProps.match.params;\n return {\n environment: environments.find(env => env.url === env_url),\n };\n}\n\nexport default compose(\n connect(mapStateToProps),\n withStyles(styles)\n)(EnvironmentDetail);\n","import React from 'react'\nimport PropTypes from 'prop-types';\nimport { withStyles } from '@material-ui/core';\n\nimport Typography from '@material-ui/core/Typography';\n\nconst styles = theme => ({\n textBlock: {\n margin: theme.spacing.unit * 5,\n marginRight: theme.spacing.unit * 10,\n marginLeft: theme.spacing.unit * 10,\n [theme.breakpoints.down('sm')]: {\n margin: theme.spacing.unit * 1,\n marginRight: theme.spacing.unit * 2,\n marginLeft: theme.spacing.unit * 2,\n },\n [theme.breakpoints.up('lg')]: {\n margin: theme.spacing.unit * 5,\n marginRight: theme.spacing.unit * 20,\n marginLeft: theme.spacing.unit * 20,\n },\n },\n title: {\n marginTop: theme.spacing.unit * 4,\n marginBottom: theme.spacing.unit,\n }\n})\n\nconst PrivatePolicy = ({ classes }) => {\n return (\n
    \n
    \n Privacy policy\n This privacy policy ("Policy") describes how Website Operator ("Website Operator", "we", "us" or "our") collects, protects and uses the personally identifiable information ("Personal Information") you ("User", "you" or "your") may provide on the scigym.ai website and any of its products or services (collectively, "Website" or "Services"). It also describes the choices available to you regarding our use of your Personal Information and how you can access and update this information. This Policy does not apply to the practices of companies that we do not own or control, or to individuals that we do not employ or manage.\n Automatic collection of information\n When you visit the Website our servers automatically record information that your browser sends. This data may include information such as your device's IP address, browser type and version, operating system type and version, language preferences or the webpage you were visiting before you came to our Website, pages of our Website that you visit, the time spent on those pages, information you search for on our Website, access times and dates, and other statistics.\n Collection of personal information\n You can visit the Website without telling us who you are or revealing any information by which someone could identify you as a specific, identifiable individual. If, however, you wish to use some of the Website's features, you will be asked to provide certain Personal Information (for example, your name and e-mail address). We receive and store any information you knowingly provide to us when you create an account, publish content, or fill any online forms on the Website. When required, this information may include your email address, name, or other Personal Information. You can choose not to provide us with your Personal Information, but then you may not be able to take advantage of some of the Website's features. Users who are uncertain about what information is mandatory are welcome to contact us.\n Managing personal information\n You are able to access, add to, update and delete certain Personal Information about you. The information you can view, update, and delete may change as the Website or Services change. When you update information, however, we may maintain a copy of the unrevised information in our records. Some information may remain in our private records after your deletion of such information from your account. We will retain and use your Personal Information for the period necessary to comply with our legal obligations, resolve disputes, and enforce our agreements unless a longer retention period is required or permitted by law. We may use any aggregated data derived from or incorporating your Personal Information after you update or delete it, but not in a manner that would identify you personally. Once the retention period expires, Personal Information shall be deleted. Therefore, the right to access, the right to erasure, the right to rectification and the right to data portability cannot be enforced after the expiration of the retention period.\n Use and processing of collected information\n Any of the information we collect from you may be used to personalize your experience; improve our Website; improve customer service and respond to queries and emails of our customers; send notification emails such as password reminders, updates, etc; run and operate our Website and Services. Information collected automatically is used only to identify potential cases of abuse and establish statistical information regarding Website usage. This statistical information is not otherwise aggregated in such a way that would identify any particular user of the system.\n We may process Personal Information related to you if one of the following applies: (i) You have given your consent for one or more specific purposes. Note that under some legislations we may be allowed to process information until you object to such processing (by opting out), without having to rely on consent or any other of the following legal bases below. This, however, does not apply, whenever the processing of Personal Information is subject to European data protection law; (ii) Provision of information is necessary for the performance of an agreement with you and/or for any pre-contractual obligations thereof; (iii) Processing is necessary for compliance with a legal obligation to which you are subject; (iv) Processing is related to a task that is carried out in the public interest or in the exercise of official authority vested in us; (v) Processing is necessary for the purposes of the legitimate interests pursued by us or by a third party. In any case, we will be happy to clarify the specific legal basis that applies to the processing, and in particular whether the provision of Personal Information is a statutory or contractual requirement, or a requirement necessary to enter into a contract.\n Information transfer and storage\n Depending on your location, data transfers may involve transferring and storing your information in a country other than your own. You are entitled to learn about the legal basis of information transfers to a country outside the European Union or to any international organization governed by public international law or set up by two or more countries, such as the UN, and about the security measures taken by us to safeguard your information. If any such transfer takes place, you can find out more by checking the relevant sections of this document or inquire with us using the information provided in the contact section.\n The rights of users\n You may exercise certain rights regarding your information processed by us. In particular, you have the right to do the following: (i) you have the right to withdraw consent where you have previously given your consent to the processing of your information; (ii) you have the right to object to the processing of your information if the processing is carried out on a legal basis other than consent; (iii) you have the right to learn if information is being processed by us, obtain disclosure regarding certain aspects of the processing and obtain a copy of the information undergoing processing; (iv) you have the right to verify the accuracy of your information and ask for it to be updated or corrected; (v) you have the right, under certain circumstances, to restrict the processing of your information, in which case, we will not process your information for any purpose other than storing it; (vi) you have the right, under certain circumstances, to obtain the erasure of your Personal Information from us; (vii) you have the right to receive your information in a structured, commonly used and machine readable format and, if technically feasible, to have it transmitted to another controller without any hindrance. This provision is applicable provided that your information is processed by automated means and that the processing is based on your consent, on a contract which you are part of or on pre-contractual obligations thereof.\n The right to object to processing\n Where Personal Information is processed for the public interest, in the exercise of an official authority vested in us or for the purposes of the legitimate interests pursued by us, you may object to such processing by providing a ground related to your particular situation to justify the objection. You must know that, however, should your Personal Information be processed for direct marketing purposes, you can object to that processing at any time without providing any justification. To learn, whether we are processing Personal Information for direct marketing purposes, you may refer to the relevant sections of this document.\n How to exercise these rights\n Any requests to exercise User rights can be directed to the Owner through the contact details provided in this document. These requests can be exercised free of charge and will be addressed by the Owner as early as possible.\n Privacy of children\n We do not knowingly collect any Personal Information from children under the age of 13. If you are under the age of 13, please do not submit any Personal Information through our Website or Service. We encourage parents and legal guardians to monitor their children's Internet usage and to help enforce this Policy by instructing their children never to provide Personal Information through our Website or Service without their permission. If you have reason to believe that a child under the age of 13 has provided Personal Information to us through our Website or Service, please contact us. You must also be at least 16 years of age to consent to the processing of your Personal Information in your country (in some countries we may allow your parent or guardian to do so on your behalf).\n Cookies\n The Website uses "cookies" to help personalize your online experience. A cookie is a text file that is placed on your hard disk by a web page server. Cookies cannot be used to run programs or deliver viruses to your computer. Cookies are uniquely assigned to you, and can only be read by a web server in the domain that issued the cookie to you. We may use cookies to collect, store, and track information for statistical purposes to operate our Website and Services. You have the ability to accept or decline cookies. Most web browsers automatically accept cookies, but you can usually modify your browser setting to decline cookies if you prefer. To learn more about cookies and how to manage them, visit internetcookies.org\n Do Not Track signals\n Some browsers incorporate a Do Not Track feature that signals to websites you visit that you do not want to have your online activity tracked. Tracking is not the same as using or collecting information in connection with a website. For these purposes, tracking refers to collecting personally identifiable information from consumers who use or visit a website or online service as they move across different websites over time. How browsers communicate the Do Not Track signal is not yet uniform. As a result, this Website is not yet set up to interpret or respond to Do Not Track signals communicated by your browser. Even so, as described in more detail throughout this Policy, we limit our use and collection of your personal information.\n Links to other websites\n Our Website contains links to other websites that are not owned or controlled by us. Please be aware that we are not responsible for the privacy practices of such other websites or third-parties. We encourage you to be aware when you leave our Website and to read the privacy statements of each and every website that may collect Personal Information.\n Information security\n We secure information you provide on computer servers in a controlled, secure environment, protected from unauthorized access, use, or disclosure. We maintain reasonable administrative, technical, and physical safeguards in an effort to protect against unauthorized access, use, modification, and disclosure of Personal Information in its control and custody. However, no data transmission over the Internet or wireless network can be guaranteed. Therefore, while we strive to protect your Personal Information, you acknowledge that (i) there are security and privacy limitations of the Internet which are beyond our control; (ii) the security, integrity, and privacy of any and all information and data exchanged between you and our Website cannot be guaranteed; and (iii) any such information and data may be viewed or tampered with in transit by a third-party, despite best efforts.\n Data breach\n In the event we become aware that the security of the Website has been compromised or users Personal Information has been disclosed to unrelated third parties as a result of external activity, including, but not limited to, security attacks or fraud, we reserve the right to take reasonably appropriate measures, including, but not limited to, investigation and reporting, as well as notification to and cooperation with law enforcement authorities. In the event of a data breach, we will make reasonable efforts to notify affected individuals if we believe that there is a reasonable risk of harm to the user as a result of the breach or if notice is otherwise required by law. When we do, we will post a notice on the Website.\n Legal disclosure\n We will disclose any information we collect, use or receive if required or permitted by law, such as to comply with a subpoena, or similar legal process, and when we believe in good faith that disclosure is necessary to protect our rights, protect your safety or the safety of others, investigate fraud, or respond to a government request.\n Changes and amendments\n We may update this Privacy Policy from time to time in our discretion and will notify you of any material changes to the way in which we treat Personal Information. When changes are made, we will revise the updated date at the bottom of this page. We may also provide notice to you in other ways in our discretion, such as through contact information you have provided. Any updated version of this Privacy Policy will be effective immediately upon the posting of the revised Privacy Policy unless otherwise specified. Your continued use of the Website or Services after the effective date of the revised Privacy Policy (or such other act specified at that time) will constitute your consent to those changes. However, we will not, without your consent, use your Personal Data in a manner materially different than what was stated at the time your Personal Data was collected. Policy was created with WebsitePolicies.\n Acceptance of this policy\n You acknowledge that you have read this Policy and agree to all its terms and conditions. By using the Website or its Services you agree to be bound by this Policy. If you do not agree to abide by the terms of this Policy, you are not authorized to use or access the Website and its Services.\n Contacting us\n If you would like to contact us to understand more about this Policy or wish to contact us concerning any matter relating to individual rights and your Personal Information, you may send an email to info@scigym.ai\n This document was last zxc@asd updated on August 30, 2019\n
    \n
    \n );\n};\n\nPrivatePolicy.propTypes = {\n classes: PropTypes.object.isRequired,\n};\n\nexport default withStyles(styles)(PrivatePolicy);\n","import React from 'react'\nimport PropTypes from 'prop-types';\nimport { withStyles } from '@material-ui/core';\n\nimport Typography from '@material-ui/core/Typography';\n\nconst styles = theme => ({\n textBlock: {\n margin: theme.spacing.unit * 5,\n marginRight: theme.spacing.unit * 10,\n marginLeft: theme.spacing.unit * 10,\n [theme.breakpoints.down('sm')]: {\n margin: theme.spacing.unit * 1,\n marginRight: theme.spacing.unit * 2,\n marginLeft: theme.spacing.unit * 2,\n },\n [theme.breakpoints.up('lg')]: {\n margin: theme.spacing.unit * 5,\n marginRight: theme.spacing.unit * 20,\n marginLeft: theme.spacing.unit * 20,\n },\n },\n title: {\n marginTop: theme.spacing.unit * 4,\n marginBottom: theme.spacing.unit,\n }\n})\n\nconst TermsAndConditions = ({ classes }) => {\n return (\n
    \n
    \n Terms and Conditions\n These terms and conditions ("Terms", "Agreement") are an agreement between Website Operator ("Website Operator", "us", "we" or "our") and you ("User", "you" or "your"). This Agreement sets forth the general terms and conditions of your use of the scigym.ai website and any of its products or services (collectively, "Website" or "Services").\n Accounts and membership\n If you create an account on the Website, you are responsible for maintaining the security of your account and you are fully responsible for all activities that occur under the account and any other actions taken in connection with it. We may, but have no obligation to, monitor and review new accounts before you may sign in and use our Services. Providing false contact information of any kind may result in the termination of your account. You must immediately notify us of any unauthorized uses of your account or any other breaches of security. We will not be liable for any acts or omissions by you, including any damages of any kind incurred as a result of such acts or omissions. We may suspend, disable, or delete your account (or any part thereof) if we determine that you have violated any provision of this Agreement or that your conduct or content would tend to damage our reputation and goodwill. If we delete your account for the foregoing reasons, you may not re-register for our Services. We may block your email address and Internet protocol address to prevent further registration.\n User content\n We do not own any data, information or material ("Content") that you submit on the Website in the course of using the Service. You shall have sole responsibility for the accuracy, quality, integrity, legality, reliability, appropriateness, and intellectual property ownership or right to use of all submitted Content. We may, but have no obligation to, monitor and review Content on the Website submitted or created using our Services by you. Unless specifically permitted by you, your use of the Website does not grant us the license to use, reproduce, adapt, modify, publish or distribute the Content created by you or stored in your user account for commercial, marketing or any similar purpose. But you grant us permission to access, copy, distribute, store, transmit, reformat, display and perform the Content of your user account solely as required for the purpose of providing the Services to you. Without limiting any of those representations or warranties, we have the right, though not the obligation, to, in our own sole discretion, refuse or remove any Content that, in our reasonable opinion, violates any of our policies or is in any way harmful or objectionable.\n Backups\n We perform regular backups of the Website and Content, however, these backups are for our own administrative purposes only and are in no way guaranteed. You are responsible for maintaining your own backups of your data. We do not provide any sort of compensation for lost or incomplete data in the event that backups do not function properly. We will do our best to ensure complete and accurate backups, but assume no responsibility for this duty.\n Links to other websites\n Although this Website may link to other websites, we are not, directly or indirectly, implying any approval, association, sponsorship, endorsement, or affiliation with any linked website, unless specifically stated herein. We are not responsible for examining or evaluating, and we do not warrant the offerings of, any businesses or individuals or the content of their websites. We do not assume any responsibility or liability for the actions, products, services, and content of any other third-parties. You should carefully review the legal statements and other conditions of use of any website which you access through a link from this Website. Your linking to any other off-site websites is at your own risk.\n Prohibited uses\n In addition to other terms as set forth in the Agreement, you are prohibited from using the Website or its Content: (a) for any unlawful purpose; (b) to solicit others to perform or participate in any unlawful acts; (c) to violate any international, federal, provincial or state regulations, rules, laws, or local ordinances; (d) to infringe upon or violate our intellectual property rights or the intellectual property rights of others; (e) to harass, abuse, insult, harm, defame, slander, disparage, intimidate, or discriminate based on gender, sexual orientation, religion, ethnicity, race, age, national origin, or disability; (f) to submit false or misleading information; (g) to upload or transmit viruses or any other type of malicious code that will or may be used in any way that will affect the functionality or operation of the Service or of any related website, other websites, or the Internet; (h) to collect or track the personal information of others; (i) to spam, phish, pharm, pretext, spider, crawl, or scrape; (j) for any obscene or immoral purpose; or (k) to interfere with or circumvent the security features of the Service or any related website, other websites, or the Internet. We reserve the right to terminate your use of the Service or any related website for violating any of the prohibited uses.\n Limitation of liability\n To the fullest extent permitted by applicable law, in no event will Website Operator, its affiliates, officers, directors, employees, agents, suppliers or licensors be liable to any person for (a): any indirect, incidental, special, punitive, cover or consequential damages (including, without limitation, damages for lost profits, revenue, sales, goodwill, use of content, impact on business, business interruption, loss of anticipated savings, loss of business opportunity) however caused, under any theory of liability, including, without limitation, contract, tort, warranty, breach of statutory duty, negligence or otherwise, even if Website Operator has been advised as to the possibility of such damages or could have foreseen such damages. To the maximum extent permitted by applicable law, the aggregate liability of Website Operator and its affiliates, officers, employees, agents, suppliers and licensors, relating to the services will be limited to an amount greater of one dollar or any amounts actually paid in cash by you to Website Operator for the prior one month period prior to the first event or occurrence giving rise to such liability. The limitations and exclusions also apply if this remedy does not fully compensate you for any losses or fails of its essential purpose.\n Indemnification\n You agree to indemnify and hold Website Operator and its affiliates, directors, officers, employees, and agents harmless from and against any liabilities, losses, damages or costs, including reasonable attorneys' fees, incurred in connection with or arising from any third-party allegations, claims, actions, disputes, or demands asserted against any of them as a result of or relating to your Content, your use of the Website or Services or any willful misconduct on your part.\n Severability\n All rights and restrictions contained in this Agreement may be exercised and shall be applicable and binding only to the extent that they do not violate any applicable laws and are intended to be limited to the extent necessary so that they will not render this Agreement illegal, invalid or unenforceable. If any provision or portion of any provision of this Agreement shall be held to be illegal, invalid or unenforceable by a court of competent jurisdiction, it is the intention of the parties that the remaining provisions or portions thereof shall constitute their agreement with respect to the subject matter hereof, and all such remaining provisions or portions thereof shall remain in full force and effect.\n Changes and amendments\n We reserve the right to modify this Agreement or its policies relating to the Website or Services at any time, effective upon posting of an updated version of this Agreement on the Website. When we do, we will revise the updated date at the bottom of this page. Continued use of the Website after any such changes shall constitute your consent to such changes. Policy was created with WebsitePolicies.\n Acceptance of these terms\n You acknowledge that you have read this Agreement and agree to all its terms and conditions. By using the Website or its Services you agree to be bound by this Agreement. If you do not agree to abide by the terms of this Agreement, you are not authorized to use or access the Website and its Services.\n Contacting us\n If you would like to contact us to understand more about this Agreement or wish to contact us concerning any matter relating to it, you may send an email to info@scigym.ai\n This document was last updated on August 30, 2019\n
    \n
    \n );\n};\n\nTermsAndConditions.propTypes = {\n classes: PropTypes.object.isRequired,\n};\n\nexport default withStyles(styles)(TermsAndConditions);\n","import types from '../utils/types';\n\nimport api from '../utils/api';\nimport { logError } from '../utils/errors';\n\nexport const getComments = params => {\n return dispatch => {\n dispatch({ type: types.GET_COMMENTS_LIST });\n api\n .getComments(params)\n .then(response => {\n dispatch({\n type: types.GET_COMMENTS_LIST_SUCCESS,\n payload: response.data,\n });\n })\n .catch(error => {\n dispatch({ type: types.GET_COMMENTS_LIST_FAILURE });\n logError(error);\n });\n };\n};\n\nexport const createComment = (...args) => {\n return dispatch => {\n dispatch({ type: types.CREATE_COMMENT });\n api\n .createComment(...args)\n .then(response => {\n dispatch({\n type: types.CREATE_COMMENT_SUCCESS,\n payload: response.data,\n });\n const messageboardId = args[1];\n dispatch(getComments(messageboardId));\n })\n .catch(error => {\n dispatch({ type: types.CREATE_COMMENT_FAILURE, payload: error });\n logError(error);\n });\n };\n};\n\nexport const editComment = (...args) => {\n return dispatch => {\n dispatch({ type: types.EDIT_COMMENT });\n api\n .editComment(...args)\n .then(response => {\n dispatch({\n type: types.EDIT_COMMENT_SUCCESS,\n payload: response.data,\n });\n const messageboardId = args[2];\n dispatch(getComments(messageboardId));\n })\n .catch(error => {\n dispatch({ type: types.EDIT_COMMENT_FAILURE, payload: error });\n logError(error);\n });\n };\n};\n\nexport const deleteComment = comment => {\n return dispatch => {\n dispatch({\n type: types.DELETE_COMMENT,\n meta: comment,\n });\n api\n .deleteComment(comment)\n .then(response => {\n dispatch({\n type: types.DELETE_COMMENT_SUCCESS,\n payload: response.data,\n });\n dispatch(getComments(comment.board.id));\n })\n .catch(error => {\n dispatch({ type: types.DELETE_COMMENT_FAILURE });\n logError(error);\n });\n };\n};\n\nexport const resetCommentsProps = () => {\n return dispatch => {\n dispatch({ type: types.RESET_COMMENTS_PROPS });\n };\n};\n\nexport const resetCommentsErrors = () => {\n return dispatch => {\n dispatch({ type: types.RESET_COMMENTS_ERRORS });\n };\n};\n\nexport const resetComments = () => {\n return dispatch => {\n dispatch({ type: types.RESET_COMMENTS })\n }\n}\n","import React from 'react';\nimport PropTypes from 'prop-types';\nimport { withStyles } from '@material-ui/core';\n\nimport TextField from '@material-ui/core/TextField';\n\nconst styles = theme => ({\n root: {\n width: '100%',\n },\n child: {\n width: 'auto',\n margin: theme.spacing.unit * 2,\n },\n textField: {\n width: '100%',\n },\n});\n\nconst CommentFormText = ({ classes, commentText, handleChange, errors }) => {\n return (\n
    \n
    \n \n
    \n
    \n );\n};\n\nCommentFormText.propTypes = {\n classes: PropTypes.object.isRequired,\n commentText: PropTypes.string,\n handleChange: PropTypes.func.isRequired,\n errors: PropTypes.any, // this is either bool or object\n};\n\nexport default withStyles(styles)(CommentFormText);\n","import React, { Component } from 'react';\nimport PropTypes from 'prop-types';\nimport { compose } from 'redux';\nimport { connect } from 'react-redux';\n\nimport { withStyles } from '@material-ui/core';\nimport DialogTitle from '@material-ui/core/DialogTitle';\nimport Dialog from '@material-ui/core/Dialog';\nimport Button from '@material-ui/core/Button';\nimport CircularProgress from '@material-ui/core/CircularProgress';\n\nimport {\n createComment,\n editComment,\n resetCommentsProps,\n} from '../../actions/comments';\nimport { getErrors } from '../../reducers/errors';\nimport CommentFormText from './CommentFormText';\nimport { isLoading } from '../../reducers/display';\nimport types from '../../utils/types';\n\nconst styles = theme => ({\n container: {\n display: 'flex',\n flexWrap: 'wrap',\n },\n loadingStyle: {\n margin: 'auto',\n padding: '5px',\n },\n});\n\nclass CommentForm extends Component {\n constructor(props) {\n super(props);\n const { comment, messageboard } = props;\n const commentExists = Boolean(comment)\n this.state = {\n commentId: commentExists ? comment.id : undefined,\n messageboardId: Boolean(messageboard) ? messageboard.id : undefined,\n commentText: commentExists ? comment.comment : '',\n };\n this.handleSubmit = this.handleSubmit.bind(this);\n this.handleChange = this.handleChange.bind(this);\n }\n\n handleSubmit = event => {\n event.preventDefault();\n const commentExists = Boolean(this.props.comment);\n const { commentText, messageboardId } = this.state;\n if (this.props.quote) {\n this.props.createComment(commentText, messageboardId);\n }\n else if (commentExists) {\n const commentId = this.props.comment.id;\n this.props.editComment(commentId, commentText, messageboardId);\n } else {\n const { commentText, messageboardId } = this.state;\n this.props.createComment(commentText, messageboardId);\n };\n }\n\n handleChange = title => event => {\n event.preventDefault();\n this.setState({\n [title]: event.target.value,\n });\n };\n\n componentDidUpdate(prevProps) {\n if (prevProps.uploadSuccess !== this.props.uploadSuccess && this.props.uploadSuccess) {\n // this is called many times in a row because the update is not fast enough\n this.props.onClose();\n this.props.resetCommentsProps();\n }\n if (this.state.messageboardId === undefined && this.props.messageboard) {\n this.setState({ messageboardId: this.props.messageboard.id })\n }\n if (this.state.commentId === undefined && this.props.comment) {\n this.setState({ commentId: this.props.comment.id })\n }\n if (this.props.quote && !prevProps.quote) {\n // here we add the quotation before the text the user is typing\n const { comment } = this.props\n const date = comment.created.split(\"T\")[0]\n const preamble = '>*posted by ' + comment.author.username + ' on ' + date + '*\\n\\n'\n let commentText = preamble + '>' + comment.comment.replace(/\\n/g, '\\n>') + ' \\n\\n'\n while (commentText.includes('\\n>\\n')) {\n commentText = commentText.replace(/\\n>\\n/g, '\\n\\n')\n }\n this.setState({ commentText: commentText })\n }\n if (!this.props.quote && prevProps.quote) {\n this.setState({ commentText: this.props.comment.comment })\n }\n }\n\n get title() {\n if (this.props.quote) {\n return 'Reply to a Comment';\n }\n if (Boolean(this.props.comment)) {\n const title = 'Edit your Comment'\n return title;\n }\n return 'Make a Comment';\n }\n\n render() {\n const { classes, errors, loading } = this.props;\n return (\n
    \n \n \n {this.title}\n \n \n {loading ? (\n \n ) : (\n \n )}\n \n
    \n );\n }\n}\n\nCommentForm.propTypes = {\n createComment: PropTypes.func.isRequired,\n editComment: PropTypes.func.isRequired,\n resetCommentsProps: PropTypes.func.isRequired,\n onClose: PropTypes.func.isRequired,\n open: PropTypes.bool.isRequired,\n uploadSuccess: PropTypes.any, // this is either undefined or bool\n loading: PropTypes.bool.isRequired,\n quote: PropTypes.bool.isRequired,\n messageboard: PropTypes.object,\n comment: PropTypes.any, // this is either undefined or an object\n errors: PropTypes.oneOfType([\n PropTypes.bool,\n PropTypes.object,\n ]).isRequired,\n};\n\nfunction mapStateToProps(state) {\n const errorsCreate = getErrors(state.errors, types.CREATE_COMMENT);\n const errorsEdit = getErrors(state.errors, types.EDIT_COMMENT);\n const errors = Boolean(errorsCreate) ? errorsCreate : Boolean(errorsEdit) && errorsEdit;\n return {\n uploadSuccess: state.comments.uploadSuccess,\n loading:\n isLoading(state.display, types.CREATE_COMMENT) ||\n isLoading(state.display, types.EDIT_COMMENT),\n errors: errors,\n }\n};\n\nconst mapDispatchToProps = {\n createComment,\n editComment,\n resetCommentsProps,\n};\n\nexport default compose(\n connect(\n mapStateToProps,\n mapDispatchToProps\n ),\n withStyles(styles)\n)(CommentForm);\n","import React, { Component } from 'react';\nimport PropTypes from 'prop-types';\nimport { connect } from 'react-redux';\nimport { compose } from 'redux';\n\nimport Dialog from '@material-ui/core/Dialog';\nimport DialogTitle from '@material-ui/core/DialogTitle';\nimport DialogActions from '@material-ui/core/DialogActions';\nimport Button from '@material-ui/core/Button';\nimport { withStyles } from '@material-ui/core';\nimport CircularProgress from '@material-ui/core/CircularProgress';\n\nimport {\n deleteComment,\n resetCommentsProps,\n} from '../../actions/comments';\nimport { isLoading } from '../../reducers/display';\nimport types from '../../utils/types';\n\nconst styles = theme => ({\n loadingStyle: {\n marginRight: '20px',\n },\n});\n\nclass CommentDelete extends Component {\n constructor(props) {\n super(props);\n this.handleDelete = this.handleDelete.bind(this);\n }\n handleDelete = () => {\n this.props.deleteComment(this.props.comment);\n };\n componentDidUpdate(prevProps) {\n if (prevProps.deleteSuccess !== this.props.deleteSuccess && this.props.deleteSuccess) {\n // this is called many times in a row because the update is not fast enough\n this.props.handleCloseDelete();\n this.props.resetCommentsProps();\n }\n }\n\n render() {\n const { classes, openDelete, handleCloseDelete, loading } = this.props;\n return (\n \n \n Are you sure you want to delete this comment?\n \n \n {loading ? (\n \n ) : (\n \n )}\n \n \n \n );\n }\n}\n\nCommentDelete.propTypes = {\n handleCloseDelete: PropTypes.func.isRequired,\n comment: PropTypes.object,\n openDelete: PropTypes.bool.isRequired,\n loading: PropTypes.bool.isRequired,\n deleteSuccess: PropTypes.any, // this is either undefined or bool\n};\n\nconst mapStateToProps = state => ({\n deleteSuccess: state.comments.deleteSuccess,\n loading: isLoading(state.display, types.DELETE_COMMENT),\n});\n\nconst mapDispatchToProps = {\n deleteComment,\n resetCommentsProps,\n};\n\nexport default compose(\n connect(\n mapStateToProps,\n mapDispatchToProps\n ),\n withStyles(styles)\n)(CommentDelete);\n","import React, { Component } from 'react';\nimport PropTypes from 'prop-types';\n\nimport Fab from '@material-ui/core/Fab';\nimport IconButton from '@material-ui/core/IconButton';\n\nimport Edit from '@material-ui/icons/Edit';\nimport Delete from '@material-ui/icons/Delete';\nimport FormatQuoteIcon from '@material-ui/icons/FormatQuote';\n\nimport CommentForm from './CommentForm';\nimport CommentDelete from './CommentDelete';\n\nclass CommentCardActions extends Component {\n constructor(props) {\n super(props);\n this.state = {\n openForm: false,\n openDelete: false,\n quote: false\n };\n }\n\n handleClickOpen = () => {\n const newStateOpen = !this.state.openForm;\n this.setState({\n openForm: newStateOpen,\n });\n };\n\n handleClickDelete = () => {\n const newStateOpen = !this.state.openDelete;\n this.setState({\n openDelete: newStateOpen,\n });\n };\n\n handleClickQuote = () => {\n const newStateOpen = !this.state.openDelete;\n this.setState({\n quote: true,\n openForm: newStateOpen,\n });\n };\n\n handleClose = () => {\n this.setState({ openForm: false, openDelete: false, quote: false });\n };\n\n render() {\n const { board } = this.props.comment;\n const { classes, owner } = this.props;\n const { openForm, openDelete } = this.state;\n return (\n
    \n {owner ? (\n
    \n \n \n \n \n \n \n \n \n \n
    \n ) : (\n \n \n \n )}\n \n \n
    \n );\n }\n};\n\nCommentCardActions.propTypes = {\n classes: PropTypes.object.isRequired,\n owner: PropTypes.bool.isRequired,\n comment: PropTypes.object.isRequired,\n};\n\nexport default CommentCardActions;\n","import React, { Component } from 'react';\nimport PropTypes from 'prop-types';\nimport { connect } from 'react-redux';\nimport { compose } from 'redux';\n\nimport ListItem from '@material-ui/core/ListItem';\nimport Card from '@material-ui/core/Card';\nimport CardActions from '@material-ui/core/CardActions';\nimport CardContent from '@material-ui/core/CardContent';\nimport { withStyles, CardActionArea } from '@material-ui/core';\nimport Slide from '@material-ui/core/Slide';\nimport Typography from '@material-ui/core/Typography';\nimport Hidden from '@material-ui/core/Hidden';\nimport NoteIcon from '@material-ui/icons/Note';\n\nimport CommentCardActions from './CommentCardActions';\nimport Markdown from '../Markdown';\n\nconst styles = theme => ({\n root: {\n display: 'flex',\n flexFlow: 'row wrap',\n maxHeight: '600px',\n overflowY: 'scroll',\n },\n cardStyle: {\n width: '100%',\n },\n logoStyle: {\n flex: '1 1 1',\n marginLeft: 'auto',\n marginRight: 'auto',\n top: '0',\n textAlign: 'center',\n padding: '30px',\n paddingLeft: '30px',\n paddingRight: '0px',\n [theme.breakpoints.up('lg')]: {\n paddingLeft: '50px',\n },\n },\n cardContentStyle: {\n flex: '1 1 400px',\n padding: '30px',\n overflowX: 'scroll',\n [theme.breakpoints.up('lg')]: {\n paddingLeft: '50px',\n },\n },\n commentContentStyle: {\n borderLeft: '0.1em solid grey',\n },\n userInfoStyle: {\n margin: theme.spacing.unit * 2\n },\n buttonStyle: {\n margin: theme.spacing.unit,\n },\n buttonPosition: {\n position: 'absolute',\n right: '40px',\n bottom: '25px',\n },\n listStyle: {\n minWidth: '770px',\n [theme.breakpoints.down('xs')]: {\n minWidth: '0px',\n },\n [theme.breakpoints.up('md')]: {\n minWidth: '950px',\n },\n },\n markdown: {\n padding: `${theme.spacing.unit * 3}px 0`,\n },\n imgStyle: {\n padding: '5px',\n height: '120px',\n width: '120px',\n [theme.breakpoints.up('lg')]: {\n height: '150px',\n width: '150px',\n },\n },\n});\n\nclass CommentCard extends Component {\n constructor(props) {\n super(props);\n this.state = {\n open: false,\n };\n }\n\n render() {\n const { comment, author, created } = this.props.comment;\n const { userId } = this.props;\n const owner = userId === author.id;\n const date = created.split(\"T\")[0]\n // const time = created.split(\"T\")[1].split(\"+\")[0].slice(0, -3) + \" CET\" // is this okay?\n const avatarUrl = 'https://avatars.githubusercontent.com/'.concat(author.username)\n const githubUrl = 'https://www.github.com/'.concat(author.username)\n const { classes } = this.props;\n return (\n \n \n \n
    \n \n\n
    \n \n \"\"\n \n {author.username}\n \n \n
    \n\n
    \n
    \n \n by {author.username} on {date}\n \n \n \n {comment + '\\n'}\n \n \n \n \n \n
    \n
    \n
    \n
    \n
    \n );\n }\n}\n\nCommentCard.propTypes = {\n key: PropTypes.string,\n comment: PropTypes.object.isRequired,\n};\n\nconst mapStateToProps = state => ({\n userId: state.user.exists ? state.user.id : undefined\n})\n\nexport default compose(\n connect(mapStateToProps),\n withStyles(styles)\n)(CommentCard);\n","import React, { Component } from 'react';\nimport PropTypes from 'prop-types';\nimport { connect } from 'react-redux';\nimport { compose } from 'redux';\n\nimport ListItem from '@material-ui/core/ListItem';\nimport Card from '@material-ui/core/Card';\nimport CardContent from '@material-ui/core/CardContent';\nimport { withStyles } from '@material-ui/core';\nimport Typography from '@material-ui/core/Typography';\nimport Hidden from '@material-ui/core/Hidden';\nimport NoteIcon from '@material-ui/icons/Note';\nimport CardActionArea from '@material-ui/core/CardActionArea';\nimport Chip from '@material-ui/core/Chip';\nimport LocalOffer from '@material-ui/icons/LocalOffer';\n\nimport Markdown from '../Markdown';\n\nconst styles = theme => ({\n root: {\n display: 'flex',\n flexFlow: 'row wrap',\n maxHeight: '600px',\n overflowY: 'scroll',\n },\n cardStyle: {\n width: '100%',\n },\n logoStyle: {\n flex: '1 1 1',\n marginLeft: 'auto',\n marginRight: 'auto',\n top: '0',\n textAlign: 'center',\n padding: '30px',\n paddingLeft: '30px',\n paddingRight: '0px',\n [theme.breakpoints.up('lg')]: {\n paddingLeft: '50px',\n },\n },\n cardContentStyle: {\n flex: '1 1 400px',\n padding: '30px',\n overflowX: 'scroll',\n [theme.breakpoints.up('lg')]: {\n paddingLeft: '50px',\n },\n },\n commentContentStyle: {\n borderLeft: '0.1em solid grey',\n },\n userInfoStyle: {\n margin: theme.spacing.unit * 2\n },\n tagStyle: {\n margin: theme.spacing.unit,\n [theme.breakpoints.down('xs')]: {\n margin: theme.spacing.unit * 0.25,\n },\n },\n listStyle: {\n minWidth: '770px',\n [theme.breakpoints.down('xs')]: {\n minWidth: '0px',\n },\n [theme.breakpoints.up('md')]: {\n minWidth: '950px',\n },\n },\n markdown: {\n padding: `${theme.spacing.unit * 3}px 0`,\n },\n imgStyle: {\n padding: '5px',\n height: '120px',\n width: '120px',\n [theme.breakpoints.up('lg')]: {\n height: '150px',\n width: '150px',\n },\n },\n});\n\nclass CommentCard extends Component {\n constructor(props) {\n super(props);\n this.state = {\n open: false,\n };\n }\n\n handleClickOpen = () => {\n const newStateOpen = !this.state.open;\n this.setState({\n open: newStateOpen,\n });\n };\n\n handleClose = () => {\n this.setState({ open: false });\n };\n\n handleClickDelete = () => {\n this.handleClose()\n };\n\n render() {\n const { title, author, created, description, tags } = this.props.messageboard;\n const { userId } = this.props;\n const date = created.split(\"T\")[0]\n // const time = created.split(\"T\")[1].split(\"+\")[0].slice(0, -3) + \" CET\" // is this okay?\n const avatarUrl = 'https://avatars.githubusercontent.com/'.concat(author.username)\n const githubUrl = 'https://www.github.com/'.concat(author.username)\n const { classes } = this.props;\n return (\n \n \n
    \n \n
    \n \n \"\"\n \n {author.username}\n \n \n
    \n
    \n
    \n \n {title}\n {tags.map(tag => (\n }\n label={tag}\n key={tag}\n clickable\n className={classes.tagStyle}\n color=\"primary\"\n variant=\"outlined\"\n />\n ))}\n \n \n by {author.username} on {date}\n \n \n \n {description + '\\n'}\n \n \n
    \n
    \n
    \n
    \n );\n }\n}\n\nCommentCard.propTypes = {\n key: PropTypes.string,\n messageboard: PropTypes.object.isRequired,\n};\n\nconst mapStateToProps = state => ({\n userId: state.user.exists ? state.user.id : undefined\n})\n\nexport default compose(\n connect(mapStateToProps),\n withStyles(styles)\n)(CommentCard);\n","import React, { Component } from 'react';\nimport { connect } from 'react-redux';\nimport { compose } from 'redux';\nimport PropTypes from 'prop-types';\n\nimport { withStyles, Button, CircularProgress } from '@material-ui/core';\nimport Typography from '@material-ui/core/Typography';\nimport Divider from '@material-ui/core/Divider';\nimport List from '@material-ui/core/List';\nimport ReplyIcon from '@material-ui/icons/Reply';\n\nimport LoginForm from '../auth/LoginForm';\nimport { getComments } from '../../actions/comments';\nimport types from '../../utils/types';\nimport { isLoading } from '../../reducers/display';\nimport CommentForm from './CommentForm';\nimport CommentCard from './CommentCard';\nimport MessageBoardComment from './MessageBoardComment';\n\n\nconst styles = theme => ({\n root: {\n display: 'flex',\n flexFlow: 'row wrap',\n backgroundColor: 'AliceBlue',\n },\n loadingStyle: {\n position: 'relative',\n margin: 'auto',\n left: '50%',\n },\n loadingContainStyle: {\n paddingTop: '50px',\n },\n content: {\n marginTop: '40px',\n margin: 'auto',\n width: '90%',\n [theme.breakpoints.up('md')]: {\n width: '75%',\n },\n paddingBottom: '50px',\n },\n title: {\n margin: theme.spacing.unit * 2,\n marginTop: theme.spacing.unit * 6,\n },\n uploadButtonStyle: {\n left: '0px',\n margin: theme.spacing.unit * 2,\n marginBottom: theme.spacing.unit * 4,\n },\n});\n\nclass Discussion extends Component {\n constructor(props) {\n super(props);\n this.state = {\n openForm: false,\n openLogin: false,\n };\n }\n\n componentDidUpdate(prevProps) {\n this.props.change && !prevProps.change && this.props.getComments(this.props.messageboard.id)\n this.props.messageboard && this.props.messageboard !== prevProps.messageboard && this.props.getComments(this.props.messageboard.id)\n }\n\n handleClickOpenLogin = () => {\n const newStateOpen = !this.state.openLogin;\n this.setState({\n openLogin: newStateOpen,\n });\n };\n\n handleClickOpenForm = () => {\n const newStateOpen = !this.state.openForm;\n this.setState({\n openForm: newStateOpen,\n });\n };\n\n handleCloseLogin = () => {\n this.setState({ openLogin: false });\n };\n\n handleCloseForm = () => {\n this.setState({ openForm: false });\n };\n\n render() {\n const { classes, messageboard, userExists, comments, loading } = this.props;\n const { openLogin, openForm } = this.state;\n const callbackURL = Boolean(messageboard) ? messageboard.titleUrl : ''\n return (\n
    \n {messageboard === undefined ? (\n \n There is no discussion here...\n \n ) : (\n
    \n
    \n \n \n {loading ? (\n
    \n \n
    \n ) : (\n \n {comments.map(comment => (\n \n \n \n \n ))}\n \n )}\n
    \n
    \n )}\n \n \n
    \n );\n }\n}\n\nDiscussion.propTypes = {\n messageboard: PropTypes.object,\n change: PropTypes.bool.isRequired,\n userExists: PropTypes.bool.isRequired,\n comments: PropTypes.arrayOf(PropTypes.object).isRequired,\n};\n\nfunction mapStateToProps(state, ownProps) {\n const { messageboards } = state.messageboards;\n let { env_url, board_url } = ownProps.match.params;\n const discussionUrl = '/env/'.concat(env_url) + '/forum/'.concat(board_url)\n const messageboard = messageboards.find(board => board.titleUrl === discussionUrl)\n const { comments } = state.comments\n return {\n messageboard: messageboard,\n userExists: state.user.exists,\n comments: comments,\n loading: isLoading(state.display, types.GET_COMMENTS_LIST)\n };\n}\n\nconst mapDispatchToProps = {\n getComments,\n};\n\nexport default compose(\n connect(mapStateToProps, mapDispatchToProps),\n withStyles(styles)\n)(Discussion);\n","import React, { Component } from 'react';\nimport { Route, Switch, withRouter } from 'react-router-dom';\nimport { connect } from 'react-redux';\nimport PropTypes from 'prop-types';\n\nimport CircularProgress from '@material-ui/core/CircularProgress';\n\nimport Auth from './auth/Auth';\nimport UserWatcher from './auth/UserWatcher';\nimport Header from './header/Header';\nimport { getApiConfig, getApiStatus } from '../actions/config';\nimport { getRepositories } from '../actions/repositories';\nimport { refreshAuthToken } from '../actions/user';\nimport { getEnvironments } from '../actions/environments';\nimport { getTopics } from '../actions/topics';\nimport { getImageConfig } from '../actions/images';\nimport { getContributors } from '../actions/contributors';\nimport { getMessageBoards, countComments } from '../actions/messageboards';\nimport Home from './home/Home';\nimport Profile from './profile/Profile';\nimport GetStarted from './get_started/GetStarted';\nimport Impressum from './impressum/Impressum';\nimport Notifications from './Notifications';\nimport constants from '../utils/constants';\nimport EnvironmentDetail from './environment/EnvironmentDetail';\nimport PrivatePolicy from './policy/PrivatePolicy';\nimport TermsAndConditions from './policy/TermsAndConditions';\nimport Discussion from './forum/Discussion';\n\nexport class App extends Component {\n constructor(props) {\n super(props);\n this.state = {\n change: false\n };\n }\n componentDidMount() {\n this.refreshAuthToken();\n this.props.getApiConfig();\n this.props.getApiStatus();\n this.props.getRepositories();\n this.props.getEnvironments();\n this.props.getTopics();\n this.props.getImageConfig();\n this.props.getContributors();\n this.props.getMessageBoards();\n this.props.countComments();\n window.setTimeout(this.props.getApiStatus, 30000); // do we need this?\n }\n\n refreshAuthToken() {\n const storedRefreshToken = localStorage.getItem(constants.REFRESH_TOKEN);\n if (storedRefreshToken) {\n this.props.refreshAuthToken(storedRefreshToken);\n }\n }\n\n componentDidUpdate(prevProps) {\n // here we force an update on any message board when it is opened.\n const prevPath = prevProps.location.pathname\n const currPath = this.props.location.pathname\n if (this.state.change) {\n this.setState({\n change: false\n })\n }\n if (currPath.includes('/env') && currPath.includes('/forum')) {\n if (prevPath !== currPath) {\n this.setState({\n change: true,\n })\n }\n }\n }\n\n render() {\n if (!this.props.appLoaded) {\n return (\n
    \n

    \n {' '}\n We are loading\n

    \n
    \n );\n }\n\n const oauthPath = new URL(this.props.githubCallbackUrl).pathname;\n const oauthPathCallBack = oauthPath.concat('env/:callbackURL')\n return (\n
    \n \n \n
    \n \n \n \n \n ()} />\n \n \n \n \n \n \n \n
    \n );\n }\n}\n\nApp.defaultProps = {\n githubCallbackUrl: null,\n};\n\nApp.propTypes = {\n appLoaded: PropTypes.bool.isRequired,\n getApiConfig: PropTypes.func.isRequired,\n getApiStatus: PropTypes.func.isRequired,\n getRepositories: PropTypes.func.isRequired,\n refreshAuthToken: PropTypes.func.isRequired,\n githubCallbackUrl: PropTypes.string,\n getEnvironments: PropTypes.func.isRequired,\n getTopics: PropTypes.func.isRequired,\n getImageConfig: PropTypes.func.isRequired,\n getContributors: PropTypes.func.isRequired,\n getMessageBoards: PropTypes.func.isRequired,\n countComments: PropTypes.func.isRequired,\n};\n\nconst mapStateToProps = state => ({\n githubCallbackUrl: state.config.githubCallbackUrl,\n appLoaded: state.config.loaded,\n});\n\nexport default withRouter(\n connect(\n mapStateToProps,\n {\n getApiConfig,\n getApiStatus,\n refreshAuthToken,\n getRepositories,\n getEnvironments,\n getTopics,\n getImageConfig,\n getContributors,\n getMessageBoards,\n countComments,\n }\n )(App)\n);\n","import types from '../utils/types';\n\nimport api from '../utils/api';\nimport { logError } from '../utils/errors';\n\nexport const getApiConfig = () => {\n return dispatch => {\n dispatch({ type: types.GET_API_CONFIG });\n api\n .config()\n .then(response => {\n dispatch({\n type: types.GET_API_CONFIG_SUCCESS,\n payload: response.data,\n });\n })\n .catch(error => {\n dispatch({ type: types.GET_API_CONFIG_FAILURE });\n logError(error);\n });\n };\n};\n\nexport const getApiStatus = () => {\n return dispatch => {\n dispatch({ type: types.GET_API_STATUS });\n api\n .status()\n .then(response => {\n dispatch({\n type: types.GET_API_STATUS_SUCCESS,\n payload: response.data,\n });\n })\n .catch(error => {\n dispatch({ type: types.GET_API_STATUS_FAILURE });\n logError(error);\n });\n };\n};\n","import types from '../utils/types';\n\nimport api from '../utils/api';\nimport { logError } from '../utils/errors';\n\nexport const getTopics = () => {\n return dispatch => {\n dispatch({ type: types.GET_TOPICS });\n api\n .topics()\n .then(response => {\n dispatch({\n type: types.GET_TOPICS_SUCCESS,\n payload: response.data,\n });\n })\n .catch(error => {\n dispatch({ type: types.GET_TOPICS_FAILURE });\n logError(error);\n });\n };\n};\n","import types from '../utils/types';\n\nimport api from '../utils/api';\nimport { logError } from '../utils/errors';\n\nexport const getContributors = params => {\n return dispatch => {\n dispatch({ type: types.GET_CONTRIBUTORS_LIST });\n api\n .getContributors(params)\n .then(response => {\n dispatch({\n type: types.GET_CONTRIBUTORS_LIST_SUCCESS,\n payload: response.data,\n });\n })\n .catch(error => {\n dispatch({ type: types.GET_CONTRIBUTORS_LIST_FAILURE });\n logError(error);\n });\n };\n};\n","// This optional code is used to register a service worker.\n// register() is not called by default.\n\n// This lets the app load faster on subsequent visits in production, and gives\n// it offline capabilities. However, it also means that developers (and users)\n// will only see deployed updates on subsequent visits to a page, after all the\n// existing tabs open on the page have been closed, since previously cached\n// resources are updated in the background.\n\n// To learn more about the benefits of this model and instructions on how to\n// opt-in, read http://bit.ly/CRA-PWA\n\nconst isLocalhost = Boolean(\n window.location.hostname === 'localhost' ||\n // [::1] is the IPv6 localhost address.\n window.location.hostname === '[::1]' ||\n // 127.0.0.1/8 is considered localhost for IPv4.\n window.location.hostname.match(/^127(?:\\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/)\n);\n\nexport function register(config) {\n if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {\n // The URL constructor is available in all browsers that support SW.\n const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href);\n if (publicUrl.origin !== window.location.origin) {\n // Our service worker won't work if PUBLIC_URL is on a different origin\n // from what our page is served on. This might happen if a CDN is used to\n // serve assets; see https://github.com/facebook/create-react-app/issues/2374\n return;\n }\n\n window.addEventListener('load', () => {\n const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;\n\n if (isLocalhost) {\n // This is running on localhost. Let's check if a service worker still exists or not.\n checkValidServiceWorker(swUrl, config);\n\n // Add some additional logging to localhost, pointing developers to the\n // service worker/PWA documentation.\n navigator.serviceWorker.ready.then(() => {\n console.log(\n 'This web app is being served cache-first by a service ' +\n 'worker. To learn more, visit http://bit.ly/CRA-PWA'\n );\n });\n } else {\n // Is not localhost. Just register service worker\n registerValidSW(swUrl, config);\n }\n });\n }\n}\n\nfunction registerValidSW(swUrl, config) {\n navigator.serviceWorker\n .register(swUrl)\n .then(registration => {\n registration.onupdatefound = () => {\n const installingWorker = registration.installing;\n if (installingWorker == null) {\n return;\n }\n installingWorker.onstatechange = () => {\n if (installingWorker.state === 'installed') {\n if (navigator.serviceWorker.controller) {\n // At this point, the updated precached content has been fetched,\n // but the previous service worker will still serve the older\n // content until all client tabs are closed.\n console.log(\n 'New content is available and will be used when all ' +\n 'tabs for this page are closed. See http://bit.ly/CRA-PWA.'\n );\n\n // Execute callback\n if (config && config.onUpdate) {\n config.onUpdate(registration);\n }\n } else {\n // At this point, everything has been precached.\n // It's the perfect time to display a\n // \"Content is cached for offline use.\" message.\n console.log('Content is cached for offline use.');\n\n // Execute callback\n if (config && config.onSuccess) {\n config.onSuccess(registration);\n }\n }\n }\n };\n };\n })\n .catch(error => {\n console.error('Error during service worker registration:', error);\n });\n}\n\nfunction checkValidServiceWorker(swUrl, config) {\n // Check if the service worker can be found. If it can't reload the page.\n fetch(swUrl)\n .then(response => {\n // Ensure service worker exists, and that we really are getting a JS file.\n const contentType = response.headers.get('content-type');\n if (\n response.status === 404 ||\n (contentType != null && contentType.indexOf('javascript') === -1)\n ) {\n // No service worker found. Probably a different app. Reload the page.\n navigator.serviceWorker.ready.then(registration => {\n registration.unregister().then(() => {\n window.location.reload();\n });\n });\n } else {\n // Service worker found. Proceed as normal.\n registerValidSW(swUrl, config);\n }\n })\n .catch(() => {\n console.log('No internet connection found. App is running in offline mode.');\n });\n}\n\nexport function unregister() {\n if ('serviceWorker' in navigator) {\n navigator.serviceWorker.ready.then(registration => {\n registration.unregister();\n });\n }\n}\n","import React from 'react';\nimport ReactDOM from 'react-dom';\nimport { Provider } from 'react-redux';\nimport { createStore, applyMiddleware, compose } from 'redux';\nimport { BrowserRouter as Router } from 'react-router-dom';\nimport thunk from 'redux-thunk';\nimport './index.css';\n\nimport auth from './middleware/auth';\nimport rootReducer from './reducers';\nimport App from './components/App';\nimport * as serviceWorker from './serviceWorker';\n\nconst middleware = applyMiddleware(thunk, auth);\nconst composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;\nconst store = createStore(rootReducer, composeEnhancers(middleware));\n\nReactDOM.render(\n \n \n \n \n ,\n document.getElementById('root')\n);\n\n// If you want your app to work offline and load faster, you can change\n// unregister() to register() below. Note this comes with some pitfalls.\n// Learn more about service workers: http://bit.ly/CRA-PWA\nserviceWorker.register();\n"],"sourceRoot":""} \ No newline at end of file