diff --git a/actions/slide/addLTI.js b/actions/slide/addLTI.js new file mode 100644 index 000000000..c3f8056db --- /dev/null +++ b/actions/slide/addLTI.js @@ -0,0 +1,33 @@ +import UserProfileStore from '../../stores/UserProfileStore'; +import {shortTitle} from '../../configs/general'; +import striptags from 'striptags'; +import TreeUtil from '../../components/Deck/TreePanel/util/TreeUtil'; +const log = require('../log/clog'); +import addActivity from '../activityfeed/addActivity'; +import {navigateAction} from 'fluxible-router'; +import Util from '../../components/common/Util'; + +export default function addLTI(context, payload, done) { + //log.info(context); + //console.log('actions/slide/addLTI.js'); + //enrich with user id + let userid = context.getStore(UserProfileStore).userid; + if (userid != null && userid !== '') { + + context.service.create('lticonsumer', payload, {timeout: 20 * 1000}, (err, res) => { + //console.log('addLTI.js'); + //console.log('res='+res); + if (err) { + console.log(err); + //console.log('ADD_LTI_FAILURE'); + context.dispatch('ADD_LTI_FAILURE', err); + } else { + context.dispatch('ADD_LTI_SUCCESS', res); + } + done(); + }); + + } //end if (userid != null && userid !== '') + else + done(); +} diff --git a/actions/user/userprofile/cancelUserlti.js b/actions/user/userprofile/cancelUserlti.js new file mode 100644 index 000000000..9403e983c --- /dev/null +++ b/actions/user/userprofile/cancelUserlti.js @@ -0,0 +1,11 @@ +import UserProfileStore from '../../../stores/UserProfileStore'; +import {navigateAction} from 'fluxible-router'; + +export default function cancelUserlti(context, payload, done) { + + context.dispatch('CANCEL_USERLTI_SUCCESS'); + context.executeAction(navigateAction, { + url: '/user/' + context.getStore(UserProfileStore).username + '/ltis/overview' + }); + +} diff --git a/actions/user/userprofile/chooseAction.js b/actions/user/userprofile/chooseAction.js index 73aa818af..d940d6fe7 100644 --- a/actions/user/userprofile/chooseAction.js +++ b/actions/user/userprofile/chooseAction.js @@ -5,13 +5,16 @@ import notFoundError from '../../error/notFoundError'; const log = require('../../log/clog'); import loadUserCollections from '../../collections/loadUserCollections'; import loadUserRecommendations from '../../recommendations/loadUserRecommendations'; -import { shortTitle } from '../../../configs/general'; +import { shortTitle, LTI_ID } from '../../../configs/general'; +import UserProfileStore from '../../../stores/UserProfileStore'; + import loadUserStats from '../../stats/loadUserStats'; 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', 'stats'], + categories: ['settings', 'groups', 'playlists', 'decks', 'recommendations', 'stats', 'ltis'], settings: ['profile', 'account', 'integrations'], groups: ['overview'], + ltis: ['overview', 'edit'], decks: ['shared'], }; @@ -19,6 +22,7 @@ export function chooseAction(context, payload, done) { log.info(context); let title = shortTitle + ' | '; + switch(payload.params.category){ case categories.categories[0]: switch(payload.params.item){ @@ -29,7 +33,7 @@ export function chooseAction(context, payload, done) { title += 'Account'; break; case categories.settings[2]: - title += 'Authorized Accounts'; + title += 'Authorized Accounts & Services'; break; default: title = shortTitle; @@ -63,6 +67,23 @@ export function chooseAction(context, payload, done) { case categories.categories[5]: title += 'User Stats'; break; + /* + case categories.categories[6]: + switch(payload.params.item){ + case categories.ltis[0]: + title += 'My Learning Services'; + break; + case categories.ltis[1]: + title += 'Add Learning Service'; + break; + default: + title = shortTitle; + break; + }; + break; + */ + + default: title = shortTitle; }; @@ -73,6 +94,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]: @@ -103,9 +125,18 @@ export function chooseAction(context, payload, done) { context.dispatch('USER_CATEGORY', {category: payload.params.category}); context.executeAction(loadUserStats, {}, callback); break; + case categories.categories[6]: + 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/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/Deck/ContentPanel/ContentActions/DownloadModal.js b/components/Deck/ContentPanel/ContentActions/DownloadModal.js index cec350702..c6dcbd2e2 100644 --- a/components/Deck/ContentPanel/ContentActions/DownloadModal.js +++ b/components/Deck/ContentPanel/ContentActions/DownloadModal.js @@ -104,6 +104,12 @@ class DownloadModal extends React.Component{ case 'HTML': return Microservices.pdf.uri +'/exportOfflineHTML/'+ splittedId[0]; break; + case 'xAPI Launch (Live)': + return Microservices.xapi.uri +'/getTinCanPackage/' + splittedId[0]+ '?offline=false&format=xml'; + break; + case 'xAPI Launch (Offline)': + return Microservices.xapi.uri +'/getTinCanPackage/' + splittedId[0]+ '?offline=true&format=xml'; + break; case 'SCORMv1.2': case 'SCORMv2': case 'SCORMv3': @@ -111,6 +117,8 @@ class DownloadModal extends React.Component{ let version = type.split('v'); //separates format from version. In second position we have the version return Microservices.pdf.uri + '/exportSCORM/' + splittedId[0]+ '?version='+version[1]; break; + + default: return ''; @@ -246,6 +254,37 @@ class DownloadModal extends React.Component{ /> + + + + + + + + + '; + } + else if(nextProps.SlideEditStore.ltiResponseHTML !== '') { + let newHTML = nextProps.SlideEditStore.ltiResponseHTML.replace(/\"/g, '\''); + iframe = ''; + } + if($('.pptx2html').length) //if slide is in canvas mode + { + $('.pptx2html').append('
'+iframe+'
'); + this.hasChanges = true; + //this.correctDimensionsBoxes('iframe'); + } + else { //if slide is in non-canvas mode + this.refs.inlineContent.innerHTML += iframe; + } + if($('.pptx2html').length) //if slide is in canvas mode + { + //this.uniqueIDAllElements(); + this.resizeDrag(); + } + } + if (nextProps.SlideEditStore.title !== '' && nextProps.SlideEditStore.title !== this.props.SlideEditStore.title && nextProps.SlideEditStore.LeftPanelTitleChange !== false) diff --git a/components/Deck/SlideEditLeftPanel/SlideEditLeftPanel.js b/components/Deck/SlideEditLeftPanel/SlideEditLeftPanel.js index 20bb477dd..aa9da0bd4 100644 --- a/components/Deck/SlideEditLeftPanel/SlideEditLeftPanel.js +++ b/components/Deck/SlideEditLeftPanel/SlideEditLeftPanel.js @@ -12,6 +12,7 @@ import mathsClick from '../../../actions/slide/mathsClick'; import codeClick from '../../../actions/slide/codeClick'; import removeBackgroundClick from '../../../actions/slide/removeBackgroundClick'; import embedClick from '../../../actions/slide/embedClick'; +import addLTI from '../../../actions/slide/addLTI'; import changeTemplate from '../../../actions/slide/changeTemplate'; import HTMLEditorClick from '../../../actions/slide/HTMLEditorClick'; import AttachQuestions from '../ContentPanel/AttachQuestions/AttachQuestionsModal'; @@ -46,6 +47,17 @@ class SlideEditLeftPanel extends React.Component { showSize: false, showTransition: false, showBackground: false, + + showLTI: false, + ltiURL: '', + ltiKey: '', + ltiWidth: '400', + ltiHeight: '300', + ltiResponseURL: '', + ltiResponseHTML: '', + ltiURLMissingError: false, + ltiKeyMissingError: false, + slideTitle: this.props.SlideEditStore.title, deckID: this.props.DeckPageStore.selector.id, slideSizeText: '', @@ -74,6 +86,7 @@ class SlideEditLeftPanel extends React.Component { if (prevState.showTemplate !== this.state.showTemplate || prevState.showOther !== this.state.showOther || prevState.showEmbed !== this.state.showEmbed || + prevState.showLTI !== this.state.showLTI || prevState.showProperties !== this.state.showProperties || prevState.showTitleChange !== this.state.showTitleChange || prevState.showSize !== this.state.showSize || @@ -84,6 +97,9 @@ class SlideEditLeftPanel extends React.Component { if (this.state.showTitleChange === true) { $('#slideTitle').focus(); + } else if (this.state.showLTI === true) + { + $('#ltiKey').focus(); } else if (this.state.showEmbed === true) { $('#embedTitle').focus(); @@ -196,6 +212,64 @@ class SlideEditLeftPanel extends React.Component { this.setState({showEmbed: false}); } } + + //LTI handles + handleLTIClick(){ + console.log('handleLTIClick'); + this.setState({showLTI: true}); + this.setState({showOther: false}); + this.forceUpdate(); + } + handleLTIAddClick(){ + //console.log('handleLTIAddClick'); + if(this.state.ltiURL === '' || this.state.ltiKey === ''){ + this.setState({ ltiURLMissingError: true }); + this.setState({ ltiKeyMissingError: true }); + //console.log('errormissing'); + this.forceUpdate(); + } + else { + //console.log('post request'); + let oauth = require('oauth-sign'); + let btoa = require('btoa'); + let timestamp = Math.round(Date.now() / 1000); + let method = 'POST'; + + let ltiURL = this.state.ltiURL; + let key = this.state.ltiKey; + let secret = this.state.ltiKey; + + let params = { + lti_message_type: 'basic-lti-launch-request', + lti_version: 'LTI-1p0', + resource_link_id: 'resourceLinkId', + oauth_consumer_key: key, + oauth_nonce: btoa(timestamp), + oauth_signature_method: 'HMAC-SHA1', + oauth_timestamp: timestamp, + oauth_version: '1.0', + ext_user_username: 'slidewiki' + }; + + let signature = oauth.hmacsign(method, ltiURL, params, secret); + params.oauth_signature = signature; + //console.log("params.oauth_signature="+params.oauth_signature); + this.context.executeAction(addLTI, { + ltiURL: ltiURL, + ltiKey: key, + ltiWidth : this.state.ltiWidth, + ltiHeight : this.state.ltiHeight, + params: params + + }); + }//end else + } + handleBackLTI(){ + this.setState({showOther: true}); + this.setState({showLTI: false}); + this.forceUpdate(); + } + handleChange(e) { this.setState({ [e.target.name]: e.target.value }); } @@ -448,6 +522,15 @@ class SlideEditLeftPanel extends React.Component { case 'handleHelpClick': this.handleHelpClick(); break; + case 'handleLTIClick': + this.handleLTIClick(); + break; + case 'handleEmbedAddClick': + this.handleEmbedAddClick(); + break; + case 'handleLTIAddClick': + this.handleLTIAddClick(); + break; case 'handleChangeBackgroundColorClick': this.handleChangeBackgroundColorClick(); break; @@ -496,6 +579,9 @@ class SlideEditLeftPanel extends React.Component { this.handleKeyPress(evt, 'handleEmbedClick')}> + this.handleKeyPress(evt, 'handleLTIClick')}> + + this.handleKeyPress(evt, 'handleTableClick')}> @@ -569,6 +655,53 @@ class SlideEditLeftPanel extends React.Component { ); + let ltiOptions = ( +
+ this.handleKeyPress(evt, 'handleBackLTI')}> + + + + +
+ + {this.state.ltiKeyMissingError === false ? '' : } + + +
+
+ and +
+ +
+ + {this.state.ltiURLMissingError === false ? '' : } + + +
+ +
+ +
+ +
+ +
+ this.handleKeyPress(evt, 'handleLTIAddClick')}> + + + +
); + const templateListStyle = { maxHeight: 600, minHeight: 320, @@ -820,6 +953,8 @@ class SlideEditLeftPanel extends React.Component { panelcontent = otherList; } else if (this.state.showEmbed) { panelcontent = embedOptions; + } else if (this.state.showLTI) { + panelcontent = ltiOptions; } else if (this.state.showProperties){ panelcontent = propertiesContent; } else if (this.state.showTitleChange){ diff --git a/components/Login/LTI.js b/components/Login/LTI.js new file mode 100644 index 000000000..c4e098a84 --- /dev/null +++ b/components/Login/LTI.js @@ -0,0 +1,55 @@ +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 = ''; +let resource_id= ''; + +class LTI extends React.Component { + + constructor(props) { + super(props); + this.componentDidMount = this.componentDidMount.bind(this); + this.state = { + queryData: this.props.currentRoute.query.data, + resource_id: this.props.currentRoute.query.resource_id + }; + cookie.save('user_json_storage', this.state.queryData, {path: '/'}); + } + + componentWillMount() { + console.log('Will be called on the server...'); + } + componentDidMount() { + console.log('LTI. componentDidMount'); + //console.log('LTI. componentDidMount.this.state.resource_Id='+this.state.resource_id); + if(this.state.resource_id !== '' && this.state.resource_id >0) { + this.context.executeAction(navigateAction, { + url: '/deck/'+this.state.resource_id + }); + } + + } + + render() { + //console.log('LTILogin.render called.resource_id='+this.state.resource_id); + return ( +
+ Welcome to LTI Login. +
+ ); + } +} + + +LTI.contextTypes = { + executeAction: React.PropTypes.func.isRequired +}; +LTI = handleRoute(LTI); +export default LTI; diff --git a/components/User/UserProfile/CategoryBox.js b/components/User/UserProfile/CategoryBox.js index 889f16ea9..241fa2b56 100644 --- a/components/User/UserProfile/CategoryBox.js +++ b/components/User/UserProfile/CategoryBox.js @@ -2,6 +2,7 @@ import PropTypes from 'prop-types'; import React from 'react'; import { NavLink } from 'fluxible-router'; import { FormattedMessage, defineMessages } from 'react-intl'; +import { LTI_ID } from '../../../configs/general'; class CategoryBox extends React.Component { constructor(props){ @@ -11,6 +12,7 @@ class CategoryBox extends React.Component { } render() { + //console.log('CategoryBox.props.username='+this.props.username); return (
@@ -37,7 +39,7 @@ class CategoryBox extends React.Component {

@@ -46,7 +48,7 @@ class CategoryBox extends React.Component {

diff --git a/components/User/UserProfile/ChangePersonalData.js b/components/User/UserProfile/ChangePersonalData.js index d8fa09f49..d91e45537 100644 --- a/components/User/UserProfile/ChangePersonalData.js +++ b/components/User/UserProfile/ChangePersonalData.js @@ -9,6 +9,7 @@ import {getLanguageName, getLanguageNativeName} from '../../../common'; import { writeCookie } from '../../../common'; import IntlStore from '../../../stores/IntlStore'; import { locales, flagForLocale }from '../../../configs/locales'; +import { LTI_ID } from '../../../configs/general'; import { Dropdown, Label } from 'semantic-ui-react'; @@ -75,7 +76,12 @@ 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'; - return ( + + //console.log("ChangePersonalData.props.username="+this.props.user.uname); + //console.log("LTI_ID="+LTI_ID); + if (!this.props.user.uname.endsWith(LTI_ID)) + { + return (
@@ -176,7 +182,15 @@ class ChangePersonalData extends React.Component {
- ); + ); + }//end if + else{ + + return ( +
+ ); + }//end else + } } diff --git a/components/User/UserProfile/Integrations.js b/components/User/UserProfile/Integrations.js index d3b993087..70f168b09 100644 --- a/components/User/UserProfile/Integrations.js +++ b/components/User/UserProfile/Integrations.js @@ -10,6 +10,11 @@ import updateProviderAction from '../../../actions/user/userprofile/updateProvid import common from '../../../common'; import { FormattedMessage, defineMessages } from 'react-intl'; import {Microservices} from '../../../configs/microservices'; +import { NavLink } from 'fluxible-router'; + +import UserProfileStore from '../../../stores/UserProfileStore'; + + const MODI = 'sociallogin_modi'; const NAME = 'sociallogin_data'; @@ -17,7 +22,6 @@ const NAME = 'sociallogin_data'; class Integrations extends React.Component { constructor(props){ super(props); - this.provider = ''; } @@ -284,7 +288,6 @@ class Integrations extends React.Component { break; } }); - // console.log('Integrations render()', this.props.providers); let facebook_icon_classes = classNames({ 'big': true, @@ -370,6 +373,8 @@ class Integrations extends React.Component {
+ +

: ''}
+ +
+ + +
+
+

+ +

+
+
+ +

+ + + + +

+ +
+ + + + ); } @@ -436,4 +471,10 @@ Integrations.contextTypes = { intl: PropTypes.object.isRequired }; +Integrations = connectToStores(Integrations, [UserProfileStore], (context, props) => { + return { + UserProfileStore: context.getStore(UserProfileStore).getState() + }; +}); + export default Integrations; diff --git a/components/User/UserProfile/UserLTIEdit.js b/components/User/UserProfile/UserLTIEdit.js new file mode 100644 index 000000000..5185bc125 --- /dev/null +++ b/components/User/UserProfile/UserLTIEdit.js @@ -0,0 +1,271 @@ +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'; +import cancelUserlti from '../../../actions/user/userprofile/cancelUserlti'; + +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, + 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); + } + + handleCancel(e) { + e.preventDefault(); + this.context.executeAction(cancelUserlti); + } + + 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 = 'Add Learning Service'; + if (this.props.currentUserlti._id !== undefined) { + header = 'Edit Learning Service'; + } + + //add creator as default member + userlist.push( +
+
+
+ +
+
+
+