From 246c061aeb9a93211b2558cd040d3d0f8921c989 Mon Sep 17 00:00:00 2001 From: urashidmir Date: Fri, 14 Sep 2018 12:32:16 +0100 Subject: [PATCH 01/39] LTI component added --- configs/routes.js | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/configs/routes.js b/configs/routes.js index a8452fc28..ea2608bfc 100644 --- a/configs/routes.js +++ b/configs/routes.js @@ -674,6 +674,19 @@ export default { done(); } }, + ltiLogin: { + path: '/ltiLogin', + method: 'get', + page: 'ltiLogin', + title: 'SlideWiki -- Login', + handler: require('../components/Login/LTI'), + action: (context, payload, done) => { + context.dispatch('UPDATE_PAGE_TITLE', { + pageTitle: shortTitle + ' | Login' + }); + done(); + } + }, deckfamily: { path: '/deckfamily/:tag', method: 'get', From 7702d509866ece6683054ee21fae5485d7273f18 Mon Sep 17 00:00:00 2001 From: urashidmir Date: Fri, 14 Sep 2018 12:33:23 +0100 Subject: [PATCH 02/39] LTI component added --- components/Login/LTI.js | 47 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 components/Login/LTI.js diff --git a/components/Login/LTI.js b/components/Login/LTI.js new file mode 100644 index 000000000..8544e6c86 --- /dev/null +++ b/components/Login/LTI.js @@ -0,0 +1,47 @@ +import React from 'react'; +import {handleRoute} from 'fluxible-router'; +import {connectToStores} from 'fluxible-addons-react'; +import {navigateAction} from 'fluxible-router'; +import ReactDOM from 'react-dom'; +import cookie from 'react-cookie'; +let classNames = require('classnames'); + +const NAME = 'ltilogin_data'; + +let queryData = ''; + +class LTI extends React.Component { + + constructor(props) { + super(props); + this.componentDidMount = this.componentDidMount.bind(this); + this.state = { + queryData: this.props.currentRoute.query.data + }; + cookie.save('user_json_storage', this.state.queryData, {path: '/'}); + } + + componentWillMount() { + console.log('Will be called on the server...'); + } + componentDidMount() { + console.log('LTI. componentDidMount'); + } + + render() { + //console.log('LTILogin.render called. queryData='+this.state.queryData); + console.log('LTILogin.render called.'); + return ( +
+ Welcome to LTI Login. +
+ ); + } +} + + +LTI.contextTypes = { + executeAction: React.PropTypes.func.isRequired +}; +LTI = handleRoute(LTI); +export default LTI; From fa01c8aaced345732e699cd6abfa9c24e735ea07 Mon Sep 17 00:00:00 2001 From: urashidmir Date: Fri, 14 Sep 2018 13:33:03 +0100 Subject: [PATCH 03/39] LTI group added --- actions/user/userprofile/chooseAction.js | 45 ++++++- components/User/UserProfile/CategoryBox.js | 101 ++++++++++++++++ .../User/UserProfile/ChangePersonalData.js | 32 +++-- components/User/UserProfile/UserProfile.js | 20 ++++ package.json | 2 + server.js | 1 + services/userProfile.js | 73 ++++++++++-- stores/UserProfileStore.js | 111 +++++++++++++++++- 8 files changed, 356 insertions(+), 29 deletions(-) diff --git a/actions/user/userprofile/chooseAction.js b/actions/user/userprofile/chooseAction.js index c07519947..fc7acb307 100644 --- a/actions/user/userprofile/chooseAction.js +++ b/actions/user/userprofile/chooseAction.js @@ -9,16 +9,24 @@ import { shortTitle } from '../../../configs/general'; import UserProfileStore from '../../../stores/UserProfileStore'; export const categories = { //Do NOT alter the order of these items! Just add your items. Used in UserProfile and CategoryBox components - categories: ['settings', 'groups', 'playlists', 'decks', 'recommendations'], + //categories: ['settings', 'groups', 'playlists', 'decks', 'recommendations'], + categories: ['settings', 'groups', 'playlists', 'decks', 'recommendations', 'ltis'], settings: ['profile', 'account', 'integrations'], groups: ['overview', 'edit'], + ltis: ['overview', 'edit'], decks: ['shared'], }; export function chooseAction(context, payload, done) { log.info(context); - + console.log('chooseAction.context='+JSON.stringify(context)); + console.log('chooseAction.payload='+JSON.stringify(payload)); + console.log('chooseAction.payload.params.username='+payload.params.username); + console.log('chooseAction.payload.params.username='+payload.params.username.endsWith('@lti.org')); + //console.log('chooseAction.payload.params.username='+this.props.UserProfileStore.user.email); let title = shortTitle + ' | '; + if(!(payload.params.username.endsWith('@lti.org'))) + { switch(payload.params.category){ case categories.categories[0]: switch(payload.params.item){ @@ -63,9 +71,29 @@ export function chooseAction(context, payload, done) { break; }; break; + case categories.categories[5]: + switch(payload.params.item){ + case categories.ltis[0]: + title += 'My LTIs'; + break; + case categories.ltis[1]: + title += 'Create LTI'; + break; + default: + title = shortTitle; + break; + }; + break; + default: title = shortTitle; }; + + } + else{ + + + }//end else context.dispatch('UPDATE_PAGE_TITLE', {pageTitle: title}); async.series([ @@ -73,6 +101,7 @@ export function chooseAction(context, payload, done) { context.executeAction(fetchUser, payload, callback); }, (callback) => { + switch (payload.params.category) { case categories.categories[0]: case categories.categories[1]: @@ -99,9 +128,21 @@ export function chooseAction(context, payload, done) { context.dispatch('USER_CATEGORY', {category: payload.params.category, item: payload.params.item}); context.executeAction(loadUserRecommendations, {}, callback); break; + + + case categories.categories[5]: + if(!categories.settings.includes(payload.params.item) && !categories.ltis.includes(payload.params.item) ){ + context.executeAction(notFoundError, {}, callback); + break; + } + context.dispatch('USER_CATEGORY', {category: payload.params.category, item: payload.params.item}); + callback(); + break; + default: context.executeAction(notFoundError, {}, callback); } + } ], (err, result) => { diff --git a/components/User/UserProfile/CategoryBox.js b/components/User/UserProfile/CategoryBox.js index 6dd57fc26..94a8424ef 100644 --- a/components/User/UserProfile/CategoryBox.js +++ b/components/User/UserProfile/CategoryBox.js @@ -3,6 +3,9 @@ import React from 'react'; import { NavLink } from 'fluxible-router'; import { FormattedMessage, defineMessages } from 'react-intl'; +import UserProfileStore from '../../../stores/UserProfileStore'; +import fetchUser from '../../../actions/user/userprofile/fetchUser'; + class CategoryBox extends React.Component { constructor(props){ super(props); @@ -10,7 +13,102 @@ class CategoryBox extends React.Component { this.headerStyle = {'backgroundColor': 'rgb(243, 244, 245)', 'color': 'rgba(0,0,0,.6)'}; } + componentDidMount(){ + //if(this.props.UserProfileStore === undefined) + //console.log("CategoryBox.this.props.UserProfileStore="+this.props.UserProfileStore); + //if(this.props.UserProfileStore.userpicture === undefined) + //this.context.executeAction(fetchUser,{ params: {username: this.props.UserProfileStore.username}, onlyPicture: true}); + } render() { + //if (this.props.UserProfileStore.user.email != 'temp@temp.com') + { + return ( +
+ +
+
+

+ +

+
+ +

+ + +

+
+ +

+ + +

+
+ +

+ + +

+
+
+ +
+
+

+ +

+
+ +

+ + +

+
+
+ + +
+
+

+ +

+
+ +

+ + +

+
+
+ +
+ ); + }//end + /* + else{ return (
@@ -74,6 +172,9 @@ class CategoryBox extends React.Component {
); + + }//end else + */ } } diff --git a/components/User/UserProfile/ChangePersonalData.js b/components/User/UserProfile/ChangePersonalData.js index d8fa09f49..ee1ad13ef 100644 --- a/components/User/UserProfile/ChangePersonalData.js +++ b/components/User/UserProfile/ChangePersonalData.js @@ -5,7 +5,7 @@ import { connectToStores } from 'fluxible-addons-react'; import CountryDropdown from '../../common/CountryDropdown.js'; import { FormattedMessage, defineMessages } from 'react-intl'; import changeUserData from '../../../actions/user/userprofile/changeUserData'; -import {getLanguageName, getLanguageNativeName} from '../../../common'; +import Iso from 'iso-639-1'; import { writeCookie } from '../../../common'; import IntlStore from '../../../stores/IntlStore'; import { locales, flagForLocale }from '../../../configs/locales'; @@ -33,7 +33,6 @@ class ChangePersonalData extends React.Component { payload.country = this.refs.country.getSelected(); payload.organization = this.refs.organization.value; payload.description = this.refs.description.value; - payload.displayName = this.refs.displayName.value; console.log(payload.language); @@ -49,7 +48,7 @@ class ChangePersonalData extends React.Component { let flag = flagForLocale(locale) || 'icon'; let options = { key: locale, - text: {getLanguageName(locale)}, + text: {Iso.getName(locale)}, value: locale, }; return options; @@ -75,6 +74,13 @@ class ChangePersonalData extends React.Component { let emailToolTipp = this.props.failures.emailNotAllowed ? this.context.intl.formatMessage(messages.emailNotAllowed) : undefined; let languageOptions = this.getLocaleOptions(); let currentLocale = (this.state.currentLocale.length <= 2) ? this.state.currentLocale : 'en'; + + console.log("ChangePersonalData.props.user.email="+this.props.user.email); + console.log("ChangePersonalData.props.username="+this.props.user.uname); + //if (this.props.user.email != 'temp@temp.com') + //if (this.props.user.uname != 'admin@lti.org') + if (!this.props.user.uname.endsWith('@lti.org')) + { return (
@@ -99,16 +105,6 @@ class ChangePersonalData extends React.Component {
-
- - -
-
); + + }//end if + else{ + + return ( +
+ ); + + }//end else + } } diff --git a/components/User/UserProfile/UserProfile.js b/components/User/UserProfile/UserProfile.js index ea852df3a..6554cf92d 100644 --- a/components/User/UserProfile/UserProfile.js +++ b/components/User/UserProfile/UserProfile.js @@ -9,6 +9,8 @@ import ChangePersonalData from './ChangePersonalData'; import IntlStore from '../../../stores/IntlStore'; import UserGroups from './UserGroups'; import UserGroupEdit from './UserGroupEdit'; +import UserLTIs from './UserLTIs'; +import UserLTIEdit from './UserLTIEdit'; import { connectToStores } from 'fluxible-addons-react'; import UserProfileStore from '../../../stores/UserProfileStore'; import PrivatePublicUserProfile from './PrivatePublicUserProfile/PrivatePublicUserProfile'; @@ -114,6 +116,17 @@ class UserProfile extends React.Component { default: return this.notImplemented(); }}); + case categories.categories[5]: + return this.addScaffold(() => {switch(this.props.UserProfileStore.categoryItem){ + case categories.ltis[0]: + return this.displayLTIs(); + break; + case categories.ltis[1]: + return this.displayLTIedit(); + break; + default: + return this.notImplemented(); + }}); default: return this.displayUserProfile(); }; @@ -237,6 +250,13 @@ class UserProfile extends React.Component { return (); } + displayLTIs() { + return (); + } + + displayLTIedit() { + return (); + } notImplemented() { return (

{ + console.log("userProfile.resource="+resource); req.reqId = req.reqId ? req.reqId : -1; log.info({Id: req.reqId, Service: __filename.split('/').pop(), Resource: resource, Operation: 'update', Method: req.method}); if (resource === 'userProfile.updatePassword') { @@ -51,8 +52,7 @@ export default { country: !isEmpty(params.country) ? params.country : '', picture: !isEmpty(params.picture) ? params.picture : '', organization: !isEmpty(params.organization) ? params.organization : '', - description: !isEmpty(params.description) ? params.description : '', - displayName: !isEmpty(params.displayName) ? params.displayName : '' + description: !isEmpty(params.description) ? params.description : '' }; rp({ method: 'PUT', @@ -141,7 +141,61 @@ export default { }) .then((body) => callback(null, body)) .catch((err) => callback(err)); - } else { + } else if (resource === 'userProfile.saveUserlti') { + console.log('userProfile.saveUserlti'); + //prepare data + if (params.members === null || params.members === undefined) + params.members = []; + let members = params.members.reduce((prev, curr) => { + let member = { + userid: curr.userid, + joined: curr.joined || '' + }; + prev.push(member); + return prev; + }, []); + let tosend = { + id: params.id, + key: params.key, + secret: !isEmpty(params.secret) ? params.secret : '', + isActive: !isEmpty(params.isActive) ? params.isActive : true, + timestamp: !isEmpty(params.timestamp) ? params.timestamp : '', + members: members, + referenceDateTime: (new Date()).toISOString() + }; + // console.log('sending:', tosend, params.jwt); + rp({ + method: 'PUT', + uri: Microservices.user.uri + '/userlti/createorupdate', + headers: { '----jwt----': params.jwt }, + json: true, + body: tosend, + timeout: body.timeout + }) + .then((body) => callback(null, body)) + .catch((err) => callback(err)); + } else if (resource === 'userProfile.deleteUserlti') { + rp({ + method: 'DELETE', + uri: Microservices.user.uri + '/userlti/' + params.ltiid, + headers: { '----jwt----': params.jwt }, + json: true, + timeout: body.timeout + }) + .then((body) => callback(null, body)) + .catch((err) => callback(err)); + } else if (resource === 'userProfile.leaveUserlti') { + rp({ + method: 'PUT', + uri: Microservices.user.uri + '/userlti/' + params.ltiid + '/leave', + headers: { '----jwt----': params.jwt }, + json: true, + timeout: body.timeout + }) + .then((body) => callback(null, body)) + .catch((err) => callback(err)); + } + else { callback('failure'); } }, @@ -199,7 +253,7 @@ export default { roles: params.roles, rootsOnly: true, sort: (params.sort || 'lastUpdate'), - status: params.status || 'any', + status: params.status || 'public', page: params.page, pageSize: 30 }, @@ -243,7 +297,7 @@ export default { }).catch((err) => callback(err)); } else { if (params.loggedInUser === params.username || params.id === params.username) { - // console.log('trying to get private user with id: ', params); + console.log('trying to get private user with id: ', params); rp({ method: 'GET', uri: Microservices.user.uri + '/user/' + params.id + '/profile', @@ -267,7 +321,7 @@ export default { hasPassword: body.hasPassword || false, providers: body.providers || [], groups: !isEmpty(body.groups) ? body.groups : [], - displayName: !isEmpty(body.displayName) ? body.displayName : '' + ltis: !isEmpty(body.ltis) ? body.ltis : [] }; callback(null, converted, { headers: { @@ -302,8 +356,7 @@ export default { country: !isEmpty(body.country) ? body.country : '', picture: !isEmpty(body.picture) ? body.picture : '', organization: !isEmpty(body.organization) ? body.organization : '', - description: !isEmpty(body.description) ? body.description : '', - displayName: !isEmpty(body.displayName) ? body.displayName : '' + description: !isEmpty(body.description) ? body.description : '' }; callback(null, converted); }) @@ -333,5 +386,5 @@ function transform(deck){ } function buildSlug(deck) { - return slugify(deck.title || '').toLowerCase() || '_'; + return slug(deck.title || '').toLowerCase() || '_'; } diff --git a/stores/UserProfileStore.js b/stores/UserProfileStore.js index a0a60a276..fe3931217 100644 --- a/stores/UserProfileStore.js +++ b/stores/UserProfileStore.js @@ -23,8 +23,7 @@ class UserProfileStore extends BaseStore { country: '', organization: '', picture: '', - description: '', - displayName: '' + description: '' }; this.userDecks = undefined; this.userDecksMeta = {}; @@ -44,10 +43,18 @@ class UserProfileStore extends BaseStore { this.currentUsergroup = {}; this.saveUsergroupError = ''; this.saveUsergroupIsLoading = false; + + this.currentUserlti = {}; + this.saveUserltiError = ''; + this.saveUserltiIsLoading = false; + this.saveProfileIsLoading = false; this.deleteUsergroupError = ''; this.usergroupsViewStatus = ''; + this.deleteUserltiError = ''; + this.userltisViewStatus = ''; + let user = dispatcher.getContext().getUser(); //console.log('UserProfileStore constructor:', user); try { @@ -82,8 +89,7 @@ class UserProfileStore extends BaseStore { country: '', organization: '', picture: '', - description: '', - displayName: '' + description: '' }; this.lastUser = ''; this.userpicture = undefined; @@ -99,10 +105,17 @@ class UserProfileStore extends BaseStore { this.currentUsergroup = {}; this.saveUsergroupError = ''; this.saveUsergroupIsLoading = false; + + this.currentUserlti = {}; + this.saveUserltiError = ''; + this.saveUserltiIsLoading = false; this.saveProfileIsLoading = false; + this.deleteUsergroupError = ''; this.usergroupsViewStatus = ''; + this.deleteUserltiError = ''; + this.userltisViewStatus = ''; //LoginModal this.showLoginModal = false; @@ -135,9 +148,18 @@ class UserProfileStore extends BaseStore { currentUsergroup: this.currentUsergroup, saveUsergroupError: this.saveUsergroupError, saveUsergroupIsLoading: this.saveUsergroupIsLoading, + + currentUserlti: this.currentUserlti, + saveUserltiError: this.saveUserltiError, + saveUserltiIsLoading: this.saveUserltiIsLoading, saveProfileIsLoading: this.saveProfileIsLoading, + deleteUsergroupError: this.deleteUsergroupError, usergroupsViewStatus: this.usergroupsViewStatus, + + deleteUserltiError: this.deleteUserltiError, + userltisViewStatus: this.userltisViewStatus, + showDeactivateAccountModal: this.showDeactivateAccountModal }; } @@ -174,6 +196,14 @@ class UserProfileStore extends BaseStore { this.saveProfileIsLoading = state.saveProfileIsLoading; this.deleteUsergroupError = state.deleteUsergroupError; this.usergroupsViewStatus = state.usergroupsViewStatus; + + this.currentUserlti = state.currentUserlti; + this.saveUserltiError = state.saveUserltiError; + this.saveUserltiIsLoading = state.saveUserltiIsLoading; + this.deleteUserltiError = state.deleteUserltiError; + this.userltisViewStatus = state.userltisViewStatus; + + this.showDeactivateAccountModal = state.showDeactivateAccountModal; } @@ -195,12 +225,17 @@ class UserProfileStore extends BaseStore { } fillInUser(payload) { + //console.log('UserProfileStore.fillInUser.payload='+JSON.stringify(payload)); + console.log('UserProfileStore.fillInUser called'); if(this.username === payload.uname) this.userpicture = payload.picture; if(!payload.onlyPicture){ Object.assign(this.user, payload); this.category = payload.category; } + this.user.email = payload.email; + console.log('UserProfileStore.fillInUser.this.user.email='+this.user.email); + this.emitChange(); } @@ -337,12 +372,28 @@ class UserProfileStore extends BaseStore { this.emitChange(); } + updateUserlti(lti) { + this.currentUserlti = lti; + console.log('UserProfileStore: updateUserlti', lti); + this.saveUserltiError = ''; + this.deleteUserltiError = ''; + this.emitChange(); + } + + saveUsergroupFailed(error) { this.saveUsergroupIsLoading = false; this.saveUsergroupError = error.message; this.emitChange(); } + saveUserltiFailed(error) { + this.saveUserltiIsLoading = false; + this.saveUserltiError = error.message; + this.emitChange(); + } + + saveUsergroupSuccess() { this.saveUsergroupIsLoading = false; this.currentUsergroup = {}; @@ -350,11 +401,23 @@ class UserProfileStore extends BaseStore { this.emitChange(); } + saveUserltiSuccess() { + this.saveUserltiIsLoading = false; + this.currentUserlti = {}; + this.saveUserltiError = ''; + this.emitChange(); + } + saveUsergroupStart() { this.saveUsergroupIsLoading = true; this.emitChange(); } + saveUserltiStart() { + this.saveUserltiIsLoading = true; + this.emitChange(); + } + saveProfileStart() { this.saveProfileIsLoading = true; this.emitChange(); @@ -369,6 +432,15 @@ class UserProfileStore extends BaseStore { this.emitChange(); } + deleteUserltiFailed(error) { + this.deleteUserltiError = { + action: 'delete', + message: error.message + }; + this.userltisViewStatus = ''; + this.emitChange(); + } + deleteUsergroupSuccess(groupid) { console.log('UserProfileStore deleteUsergroupSuccess: delete % from %', groupid, this.user.groups); //remove group from user @@ -383,11 +455,30 @@ class UserProfileStore extends BaseStore { this.emitChange(); } + deleteUserltiSuccess(ltiid) { + console.log('UserProfileStore deleteUserltiSuccess: delete % from %', ltiid, this.user.ltis); + //remove lti from user + let ltis = this.user.ltis.reduce((prev, curr) => { + if (curr._id.toString() !== ltiid.toString()) + prev.push(curr); + return prev; + }, []); + this.user.ltis = ltis; + this.deleteUserltiError = ''; + this.userltisViewStatus = ''; + this.emitChange(); + } + updateUsergroupsStatus() { this.usergroupsViewStatus = 'pending'; this.emitChange(); } + updateUserltisStatus() { + this.userltisViewStatus = 'pending'; + this.emitChange(); + } + setUserDecksLoading(){ this.userDecks = undefined; // preserve sorting of sort dropdown during loading @@ -470,6 +561,18 @@ UserProfileStore.handlers = { 'UPDATE_USERGROUPS_STATUS': 'updateUsergroupsStatus', 'LEAVE_USERGROUP_FAILED': 'deleteUsergroupFailed', 'LEAVE_USERGROUP_SUCCESS': 'deleteUsergroupSuccess', + + //LTI + 'UPDATE_USERLTI': 'updateUserlti', + 'SAVE_USERLTI_START': 'saveUserltiStart', + 'SAVE_USERLTI_FAILED': 'saveUserltiFailed', + 'SAVE_USERLTI_SUCCESS': 'saveUserltiSuccess', + 'DELETE_USERLTI_FAILED': 'deleteUserltiFailed', + 'DELETE_USERLTI_SUCCESS': 'deleteUserltiSuccess', + 'UPDATE_USERLTIS_STATUS': 'updateUserltisStatus', + 'LEAVE_USERLTI_FAILED': 'deleteUserltiFailed', + 'LEAVE_USERLTI_SUCCESS': 'deleteUserltiSuccess', + 'SAVE_USERPROFILE_START': 'saveProfileStart', 'SHOW_DEACTIVATE_ACCOUNT_MODAL': 'showDeactivateModal', 'HIDE_DEACTIVATE_ACCOUNT_MODAL': 'hideDeactivateModal' From caa3a3f960301309d3d1402d5abd2f923efca8f4 Mon Sep 17 00:00:00 2001 From: urashidmir Date: Fri, 14 Sep 2018 13:35:15 +0100 Subject: [PATCH 04/39] LTI groups added --- actions/user/userprofile/deleteUserlti.js | 14 ++ actions/user/userprofile/leaveUserlti.js | 14 ++ actions/user/userprofile/saveUserlti.js | 18 ++ actions/user/userprofile/updateUserlti.js | 27 ++ components/User/UserProfile/UserLTIEdit.js | 280 +++++++++++++++++++++ components/User/UserProfile/UserLTIs.js | 167 ++++++++++++ services/userlti.js | 37 +++ 7 files changed, 557 insertions(+) create mode 100644 actions/user/userprofile/deleteUserlti.js create mode 100644 actions/user/userprofile/leaveUserlti.js create mode 100644 actions/user/userprofile/saveUserlti.js create mode 100644 actions/user/userprofile/updateUserlti.js create mode 100644 components/User/UserProfile/UserLTIEdit.js create mode 100644 components/User/UserProfile/UserLTIs.js create mode 100644 services/userlti.js diff --git a/actions/user/userprofile/deleteUserlti.js b/actions/user/userprofile/deleteUserlti.js new file mode 100644 index 000000000..2418198bf --- /dev/null +++ b/actions/user/userprofile/deleteUserlti.js @@ -0,0 +1,14 @@ +import UserProfileStore from '../../../stores/UserProfileStore'; + +export default function deleteUserlti(context, payload, done) { + context.dispatch('UPDATE_USERLTIS_STATUS', null); + payload.jwt = context.getStore(UserProfileStore).jwt; + context.service.update('userProfile.deleteUserlti', payload, { timeout: 20 * 1000 }, (err, res) => { + if (err) { + context.dispatch('DELETE_USERLTI_FAILED', err); + } else { + context.dispatch('DELETE_USERLTI_SUCCESS', payload.ltiid); + } + done(); + }); +} diff --git a/actions/user/userprofile/leaveUserlti.js b/actions/user/userprofile/leaveUserlti.js new file mode 100644 index 000000000..720a8450e --- /dev/null +++ b/actions/user/userprofile/leaveUserlti.js @@ -0,0 +1,14 @@ +import UserProfileStore from '../../../stores/UserProfileStore'; + +export default function leaveUserlti(context, payload, done) { + context.dispatch('UPDATE_USERLTIS_STATUS', null); + payload.jwt = context.getStore(UserProfileStore).jwt; + context.service.update('userProfile.leaveUserlti', payload, { timeout: 20 * 1000 }, (err, res) => { + if (err) { + context.dispatch('LEAVE_USERLTI_FAILED', err); + } else { + context.dispatch('LEAVE_USERLTI_SUCCESS', payload.ltiid); + } + done(); + }); +} diff --git a/actions/user/userprofile/saveUserlti.js b/actions/user/userprofile/saveUserlti.js new file mode 100644 index 000000000..cf1cec2ca --- /dev/null +++ b/actions/user/userprofile/saveUserlti.js @@ -0,0 +1,18 @@ +import UserProfileStore from '../../../stores/UserProfileStore'; +import {navigateAction} from 'fluxible-router'; + +export default function saveUserlti(context, payload, done) { + context.dispatch('SAVE_USERLTI_START', {}); + payload.jwt = context.getStore(UserProfileStore).jwt; + context.service.update('userProfile.saveUserlti', payload, {timeout: 60 * 1000}, { timeout: 60 * 1000 }, (err, res) => { + if (err) { + context.dispatch('SAVE_USERLTI_FAILED', err); + } else { + context.dispatch('SAVE_USERLTI_SUCCESS', res); + context.executeAction(navigateAction, { + url: '/user/' + context.getStore(UserProfileStore).username + '/ltis/overview' + }); + } + done(); + }); +} diff --git a/actions/user/userprofile/updateUserlti.js b/actions/user/userprofile/updateUserlti.js new file mode 100644 index 000000000..1740d0d29 --- /dev/null +++ b/actions/user/userprofile/updateUserlti.js @@ -0,0 +1,27 @@ +const log = require('../../log/clog'); +import { shortTitle } from '../../../configs/general'; + +export default function updateUserlti(context, payload, done) { + log.info(context); + + if (payload.offline) { + context.dispatch('UPDATE_USERLTI', payload.lti); + context.dispatch('UPDATE_PAGE_TITLE', {pageTitle: shortTitle + ' | Edit LTI ' + payload.lti.name}); + return done(); + } + + let payload2 = { + ltiid: payload.lti._id + }; + + context.service.read('userlti.read', payload2, { timeout: 20 * 1000 }, (err, res) => { + if (err) { + context.dispatch('UPDATE_USERLTI', payload.lti); + } + else { + context.dispatch('UPDATE_USERLTI', res[0]); + context.dispatch('UPDATE_PAGE_TITLE', {pageTitle: shortTitle + ' | Edit LTI ' + res[0].name}); + } + done(); + }); +} diff --git a/components/User/UserProfile/UserLTIEdit.js b/components/User/UserProfile/UserLTIEdit.js new file mode 100644 index 000000000..abf4ed6ac --- /dev/null +++ b/components/User/UserProfile/UserLTIEdit.js @@ -0,0 +1,280 @@ +import PropTypes from 'prop-types'; +import React from 'react'; +import { Microservices } from '../../../configs/microservices'; +import {NavLink, navigateAction} from 'fluxible-router'; +import { TextArea } from 'semantic-ui-react'; +import { timeSince } from '../../../common'; +import UserPicture from '../../common/UserPicture'; +import updateUserlti from '../../../actions/user/userprofile/updateUserlti'; +import saveUserlti from '../../../actions/user/userprofile/saveUserlti'; + +class UserLTIEdit extends React.Component { + constructor(props){ + super(props); + + this.styles = {'backgroundColor': '#2185D0', 'color': 'white'}; + } + + componentDidUpdate() { + // console.log('UserLTIEdit componentDidUpdate:', this.props.saveUserLTIError, this.props.currentUserlti); + if (this.props.saveUserltiError) { + swal({ + title: 'Error', + text: 'Unknown error while saving.', + type: 'error', + confirmButtonText: 'Close', + confirmButtonClass: 'negative ui button', + allowEscapeKey: true, + allowOutsideClick: true, + buttonsStyling: false + }) + .then(() => { + return true; + }) + .catch(); + return; + } + this.refs.LTIKey.value = this.props.currentUserlti.key || ''; + this.refs.LTISecret.value = this.props.currentUserlti.secret || ''; + } + + componentDidMount() { + $('#userlti_edit_dropdown_usernames_remote') + .dropdown({ + apiSettings: { + url: Microservices.user.uri + '/information/username/search/{query}', + cache: false + }, + saveRemoteData: false, + action: (name, value, source) => { + // console.log('dropdown select', name, value, source); + + $('#userlti_edit_dropdown_usernames_remote').dropdown('clear'); + $('#userlti_edit_dropdown_usernames_remote').dropdown('hide'); + + let lti = this.getLTI(this.props.currentUserlti.members); + if (lti.members === undefined || lti.members === null) + lti.members = []; + + let data = JSON.parse(decodeURIComponent(value)); + console.log('trying to add', name, 'to', lti.members, ' with ', data); + if (lti.members.findIndex((member) => { + return member.userid === parseInt(data.userid); + }) === -1 && data.username !== this.props.username) { + lti.members.push({ + username: data.username, + userid: parseInt(data.userid), + joined: data.joined || undefined, + picture: data.picture, + country: data.country, + organization: data.organization + }); + } + + this.context.executeAction(updateUserlti, {lti: lti, offline: true}); + + return true; + } + }); + } + + getLTI(members = undefined) { + /* + let lti = { + _id: this.props.currentUserlti._id, + name: this.refs.LTIName.value, + description: this.refs.LTIDescription.value, + members: members, + timestamp: this.props.currentUserlti.timestamp || '', + creator: this.props.currentUserlti.creator || this.props.userid + }; + */ + + let lti = { + _id: this.props.currentUserlti._id, + key: this.refs.LTIKey.value, + secret: this.refs.LTISecret.value, + members: members, + timestamp: this.props.currentUserlti.timestamp || '', + creator: this.props.currentUserlti.creator || this.props.userid + }; + + if (this.props.currentUserlti._id) + lti.id = lti._id; + + //TODO get members from list + + return lti; + } + + handleClickOnEditLTI(e) { + e.preventDefault(); + } + + handleSave(e) { + e.preventDefault(); + + let lti = this.getLTI(this.props.currentUserlti.members); + + console.log('handleSave:', lti); + + if (lti.key === '') { + swal({ + title: 'Error', + text: 'At least you have to specify the LTI Key.', + type: 'error', + confirmButtonText: 'Close', + confirmButtonClass: 'negative ui button', + allowEscapeKey: true, + allowOutsideClick: true, + buttonsStyling: false + }) + .then(() => { + return true; + }) + .catch(); + return; + } + + this.context.executeAction(saveUserlti, lti); + } + + handleClickRemoveMember(member) { + // console.log('handleClickRemoveMember', member, 'from', this.props.currentUserlti.members); + + let lti = this.getLTI(this.props.currentUserlti.members); + + lti.members = lti.members.filter((gmember) => { + return gmember.userid !== member.userid; + }); + + this.context.executeAction(updateUserlti, {lti: lti, offline: true}); + } + + render() { + console.log("UserLTIEdit rendered"); + const signUpLabelStyle = {width: '150px'}; + + let userlist = []; + //change header and data depending on lti should be created or edited + let header = 'Create LTI'; + if (this.props.currentUserlti._id !== undefined) { + header = 'Edit LTI'; + } + + //add creator as default member + userlist.push( +
+
+
+ +
+
+
+