Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 22 additions & 1 deletion imports/startup/server/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -249,7 +249,8 @@ Meteor.startup(() => {
assessed: false,
removed: true,
teamFormationTime: 0,
peerAssessmentTime: 0
peerAssessmentTime: 0,
currentQuestions: teams[i].map(pid => ({ pid, question_ind:0}))
});

for (let j = 0; j < teams[i].length; j++) {
Expand Down Expand Up @@ -552,6 +553,26 @@ Meteor.methods({
}
},

'questions.setCurrent': function(team_id, member_pid, question_ind){
// update the team of interest with the new members
console.log(team_id);
console.log(member_pid);
console.log(question_ind);
Teams.update({
_id: team_id,
"currentQuestions.pid": member_pid
},
{
$set: { "currentQuestions.$.question_ind" : question_ind }
},
(error) => {
if (error) {
throw new Meteor.Error("failed-to-set-current-question",
"Unable to set current question for member");
}
});
},

'users.addPoints': function({ user_id, session_id, points }) {
Users.update(
{
Expand Down
128 changes: 6 additions & 122 deletions imports/startup/server/team-former.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,30 +7,6 @@ import { shuffle } from './helper-funcs';
Will return an array of arrays, with each internal array have pids representing a team
*/
function firstRoundTeams(participants, max_team_size) {

// shake them up
shuffle(participants);

// edge case, <= max_team_size+1 people, just return them in an array of arrays
if (participants.length <= max_team_size + 1) {
return [participants];
}

// edge case when max_team_size is 3, uneven teams
if (max_team_size === 3 && participants.length === 5) {
return [participants.slice(0,3), participants.slice(3)];
}

// TODO: check if this is good enough
// edge case when max_team_size is 5, uneven teams
if (max_team_size === 5 && participants.length === 7) {
return [participants.slice(0,3), participants.slice(3)];
}

// edge case, not enough teams to make full size teams
if (participants.length % max_team_size > Math.floor(participants.length / max_team_size) ) {
max_team_size = max_team_size - 1;
}

// list to hold all of our teams and one to keep track of grouped people
let teams = [];
Expand All @@ -42,17 +18,12 @@ function firstRoundTeams(participants, max_team_size) {
// teams of max_team_size
let nextTeam = [];
while (nextTeam.length != max_team_size) {
nextTeam[nextTeam.length] = ungrouped.pop();
nextTeam[nextTeam.length] = ungrouped.shift();
}
teams.push(nextTeam);
}

// handles uneven groups
let addIndex = 0;
while (ungrouped.length > 0) {
teams[addIndex][teams[addIndex].length] = ungrouped.pop();
addIndex++;
}
teams.push(ungrouped);

console.log(teams);
// return our completed teams
Expand All @@ -62,97 +33,10 @@ function firstRoundTeams(participants, max_team_size) {

// builds teams for subsequent rounds of team formation (where duplicates are avoided)
function buildNewTeams(participants, teamHistory, max_team_size) {

// edge case, when num_participants < 6, can't guarantee uniqueness so just make random
if (participants.length % max_team_size > Math.floor(participants.length / max_team_size) ) {
return firstRoundTeams(participants, max_team_size);
}

// shake 'em up
shuffle(participants);

// list to hold all of our teams and one to keep track of grouped people
let teams = [];
var ungrouped = participants.slice(0);

// build floor(num_participants / max_team_size) teams
while (teams.length != Math.floor(participants.length / max_team_size)) {
// teams of max_team_size
let nextTeam = [ungrouped.pop()];
while (nextTeam.length != max_team_size) {
// we want people that this team has least recently worked with
let best_wup = {
"person": "",
"weight": Number.MAX_SAFE_INTEGER
};
// check ungrouped people
for (let i = 0; i < ungrouped.length; i++) {
// find weight of this ungrouped person in the adj lists of each of the members of the team
let wup = {
"person": ungrouped[i],
"weight": teamHistory[nextTeam[0]][ungrouped[i]]
};
for (let j = 1; j < nextTeam.length; j++) {
wup["weight"] = wup["weight"] + teamHistory[nextTeam[j]][ungrouped[i]]
}
// check if this is the best option that we have found so far
if (wup["weight"] < best_wup["weight"]) {
best_wup = wup;
}
// break when we find someone that we have't worked with
if (best_wup["weight"] === 0) break;
}
// add the best person that we found
nextTeam[nextTeam.length] = best_wup["person"];
// remove them from the ungrouped list
now_ungrouped = new Array(ungrouped.length - 1);
for (let i = 0, j = 0; i < ungrouped.length; i++) {
if (ungrouped[i] != best_wup["person"]) {
now_ungrouped[j] = ungrouped[i];
j++;
}
}
ungrouped = now_ungrouped;
//ungrouped = ungrouped.filter((person) => person != best_wup["person"]);
}
teams[teams.length] = nextTeam;
}

// handles uneven groups
while (ungrouped.length > 0) {
let leftover_person = ungrouped.pop()
// we want to join the team where the ungrouped addition would affect the weight of the team the least
best_team_match = {
"team_idx": -1,
"weight": Number.MAX_SAFE_INTEGER
};
// check each team
for (let i = 0; i < teams.length; i++) {
// stop at teams of size max_team_size + 1
if (teams[i].length > max_team_size) continue;
// gather the weight of this team to see potential fit
team_match = {
"team_idx": i,
"weight": teamHistory[leftover_person][teams[i][0]]
}
for (let j = 1; j < teams[i].length; j++) {
team_match["weight"] = team_match["weight"] + teamHistory[leftover_person][teams[i][j]];
}
if (team_match["weight"] < best_team_match["weight"]) {
best_team_match = team_match;
}
// if we found a perfect team, get out (save some iterations)
if (best_team_match["weight"] === 0) {
break;
}
}
// add the ungrouped user to the best-matching team that we found
teams[best_team_match["team_idx"]][(teams[best_team_match["team_idx"]]).length] = leftover_person;
}

console.log(teams);
// return our completed teams
return teams;
const first = participants.shift();
participants.push(first);

return firstRoundTeams(participants, max_team_size)
}

export function formTeams(session_id, prevActIndex, max_team_size) {
Expand Down
116 changes: 116 additions & 0 deletions imports/ui/Activities/Components/QuestionCarousel/QuestionCarousel.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
import ReactSwipe from 'react-swipe';
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import { Textfit } from 'react-textfit';
import './QuestionCarousel.scss';

class QuestionCarousel extends Component {
constructor(props) {
super(props);
this.state = {
prevQuestionIndex: 0,
startTime: new Date().getTime(),
};
}

static propTypes = {
pid: PropTypes.string.isRequired,
_id: PropTypes.string.isRequired,
questions: PropTypes.array.isRequired,
currentQuestions: PropTypes.array.isRequired,
};

onSlideChange = () => {
const endTime = new Date().getTime();
const { startTime } = this.state;

const { questions, _id, pid } = this.props;

const past_question = questions[this.state.prevQuestionIndex]._id;
const next_question = questions[this.reactSwipeEl.getPos()]._id;

//update questions
Meteor.call('questions.updateTimers', past_question, next_question, startTime, endTime, error => {
if (!error) console.log('Tracked questions successfully');
else console.log(error);
});

// keep track of this current question and when it began
this.setState({
prevQuestionIndex: this.reactSwipeEl.getPos(),
startTime: new Date().getTime()
});

Meteor.call('questions.setCurrent', _id, pid, this.reactSwipeEl.getPos(), error => {
if (!error) console.log('Set current question successfully');
else console.log(error);
});
};

getCurrentQuestion() {
const { pid, currentQuestions } = this.props;
for (var i = 0; i < currentQuestions.length; i++) {
if (currentQuestions[i].pid == pid) {
return currentQuestions[i].question_ind;
}
}
return 0;
}

componentWillUnmount() {
const { startTime, prevQuestionIndex } = this.state;
const { questions } = this.props;
const endTime = new Date().getTime();

if (questions.length != 0) {
Meteor.call('questions.updateTimers', questions[prevQuestionIndex]._id, '', startTime, endTime, error => {
if (!error) console.log('Tracked final question successfully');
else console.log(error);
});
}
}

render() {
return (
<div>
<div className="swipe-instr-top">
<Textfit mode="multi" max={36}>
{this.props.title}
</Textfit>
</div>
<div className="swipe-subinstr-top">
<strong>Swipe</strong> to see more questions
</div>
<div className="slider-main">
<ReactSwipe
className="carousel"
swipeOptions={{ continuous: true, callback: this.onSlideChange, startSlide: this.getCurrentQuestion() }}
ref={el => (this.reactSwipeEl = el)}
>
{this.props.questions.map((q, index) => {
return (
<div className="question-card-wrapper" key={q._id}>
<div className="question-card">
<div className="label" style={{ background: q.color }}>
{q.label}
</div>
{index + 1}. {q.prompt}
</div>
</div>
);
})}
</ReactSwipe>

<button className="prev" type="button" onClick={() => this.reactSwipeEl.prev()}>
&larr;
</button>
<button className="next" type="button" onClick={() => this.reactSwipeEl.next()}>
&rarr;
</button>
</div>
</div>
);
}
}

export default QuestionCarousel;
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
@import '../../../../ui/assets/main';

.swipe-instr-top {
margin: 0 auto 0;
font-weight: 600;
width: 100%;
}

.swipe-subinstr-top {
text-align: center;
font-size: 0.8em;
color: #808080cc;
margin: 0;
}

.swipe-instr-bottom {
align-self: center;
position: absolute;
bottom: 2vh;
//margin: 1em auto 0;
}

.slider-main {
// width: 100vw;
height: 70vh; // TODO: fix this!!

button {
position: absolute;
border: none;
background: none;
font-size: 30px;
bottom: 2vh;
}

.next {
right: 2vw;
}

.prev {
left: 2vw;
}
}

.question-card {
margin: 1.1em 0.9em;
padding: 1em;
border: 2px solid $dynamic-black;
border-radius: 5px;
position: relative;
display: flex;
flex-direction: column;

.suggest-question-tag {
border: 2px solid black;
margin-top: 0.5em;
/* color: white; */
width: fit-content;
padding: 0.2em;
border-radius: 5px;
/* bottom: -1em; */
/* right: -1em; */
font-weight: 600;
align-self: flex-end;
-webkit-transition: background 150ms; /* For Safari 3.1 to 6.0 */
transition: background 150ms;
}

.suggest-question-tag:active {
background: $dynamic-red;
}

.label {
//background: white;
position: absolute;
top: -0.8em;
left: 0.8rem;
color: white;
//bottom: -0.8em;
//right: -0.3em;
//align-self: center;
font-size: 0.8em;
border-radius: $button-border-radius;
//background: $dynamic-blue;
padding: 0rem 0.2rem;
line-height: inherit;
}

}
Loading