From c4fe884eb6ce95d6494752c3151642214c81eb2b Mon Sep 17 00:00:00 2001 From: Kai Zhe Date: Sun, 24 Mar 2019 10:59:04 +0800 Subject: [PATCH 1/9] Removed previous button on first question, added divider --- src/components/incubator/EditingWorkspace.tsx | 52 +++++++++++-------- .../editingWorkspaceSideContent/HelpTab.tsx | 31 +++++++++++ .../ManageQuestionTab.tsx | 6 +-- .../TextareaContent.tsx | 1 - src/components/workspace/ControlBar.tsx | 2 +- 5 files changed, 65 insertions(+), 27 deletions(-) create mode 100644 src/components/incubator/editingWorkspaceSideContent/HelpTab.tsx diff --git a/src/components/incubator/EditingWorkspace.tsx b/src/components/incubator/EditingWorkspace.tsx index 8e4ced7835..a6e5a83c02 100644 --- a/src/components/incubator/EditingWorkspace.tsx +++ b/src/components/incubator/EditingWorkspace.tsx @@ -30,6 +30,7 @@ import { QuestionTemplateTab, TextareaContentTab } from './editingWorkspaceSideContent'; +import HelpTab from './editingWorkspaceSideContent/HelpTab'; export type AssessmentWorkspaceProps = DispatchProps & OwnProps & StateProps; @@ -288,58 +289,65 @@ class AssessmentWorkspace extends React.Component ) }, { - label: `${assessment!.category} Briefing`, - icon: IconNames.BRIEFCASE, + label: `Add/Delete Questions`, + icon: IconNames.WRENCH, body: ( - ) }, { - label: `Question Template`, - icon: IconNames.DOCUMENT, + label: `Manage Global Deployment`, + icon: IconNames.GLOBE, body: ( - ) }, { - label: `Manage Question`, - icon: IconNames.WRENCH, + label: `Help`, + icon: IconNames.HELP, body: ( - + ) + }, + { + label: `Task ${questionId + 1}`, + icon: IconNames.NINJA, + body: ( + ) }, { - label: `Manage Global Deployment`, - icon: IconNames.GLOBE, + label: `Question Template`, + icon: IconNames.DOCUMENT, body: ( - ) }, diff --git a/src/components/incubator/editingWorkspaceSideContent/HelpTab.tsx b/src/components/incubator/editingWorkspaceSideContent/HelpTab.tsx new file mode 100644 index 0000000000..7863b6e9af --- /dev/null +++ b/src/components/incubator/editingWorkspaceSideContent/HelpTab.tsx @@ -0,0 +1,31 @@ +// import { IconNames } from '@blueprintjs/icons'; +import * as React from 'react'; + +export class HelpTab extends React.Component<{}, {}> { + public constructor(props: {}) { + super(props); + } + + public render() { + return this.helpTab(); + } + + private helpTab = () => { + return ( +
+ Mission-specific tabs:
+ Modify mission brief in the ission Briefing tab by clicking on the text
+ Insert questions at the current question index or delete the current question in the Add/Delete Questions tab
+ The Source version, library and globals for all questions in the mission can be modified in Global Deployment
+
+ Question-specific tabs:
+ Modify question description in Task tab
+ Modify question template in Question template tab
+ The Source version, library and globals for the current question can be modified in Global Deployment
+ Modify grading in Grading tab
+
+ ); + }; +} + +export default HelpTab; diff --git a/src/components/incubator/editingWorkspaceSideContent/ManageQuestionTab.tsx b/src/components/incubator/editingWorkspaceSideContent/ManageQuestionTab.tsx index 043a646d72..4bfed79c6c 100644 --- a/src/components/incubator/editingWorkspaceSideContent/ManageQuestionTab.tsx +++ b/src/components/incubator/editingWorkspaceSideContent/ManageQuestionTab.tsx @@ -24,12 +24,12 @@ export class ManageQuestionTab extends React.Component { return (
{controlButton( - 'Make Programming Question', + 'Insert Programming Question', IconNames.FONT, this.makeQuestion(programmingTemplate) )} - {controlButton('Make MCQ Question', IconNames.CONFIRM, this.makeQuestion(mcqTemplate))} - {controlButton('Make Programming Question', IconNames.REMOVE, this.deleteQn)} + {controlButton('Insert MCQ Question', IconNames.CONFIRM, this.makeQuestion(mcqTemplate))} + {controlButton('Delete Current Question', IconNames.REMOVE, this.deleteQn)}
); }; diff --git a/src/components/incubator/editingWorkspaceSideContent/TextareaContent.tsx b/src/components/incubator/editingWorkspaceSideContent/TextareaContent.tsx index 9d84982776..7d98de8b81 100644 --- a/src/components/incubator/editingWorkspaceSideContent/TextareaContent.tsx +++ b/src/components/incubator/editingWorkspaceSideContent/TextareaContent.tsx @@ -12,7 +12,6 @@ interface IProps { path: Array; processResults?: (str: string | number) => string | number; useRawValue?: boolean; - processResults?: (newVal: string | number) => string | number; updateAssessment: (assessment: IAssessment) => void; } diff --git a/src/components/workspace/ControlBar.tsx b/src/components/workspace/ControlBar.tsx index 5bf80be90e..899b15d08b 100755 --- a/src/components/workspace/ControlBar.tsx +++ b/src/components/workspace/ControlBar.tsx @@ -201,7 +201,7 @@ class ControlBar extends React.PureComponent { } private hasPreviousButton() { - return this.props.questionProgress && this.props.questionProgress[0] > 0; + return this.props.questionProgress && this.props.questionProgress[0] > 1; } private hasReturnButton() { From 15d87493a9604814e0a5b7fad0faa9a5959761ee Mon Sep 17 00:00:00 2001 From: Kai Zhe Date: Sun, 24 Mar 2019 11:00:24 +0800 Subject: [PATCH 2/9] Yarn format --- src/components/incubator/EditingWorkspace.tsx | 4 +--- .../editingWorkspaceSideContent/HelpTab.tsx | 23 +++++++++++-------- 2 files changed, 14 insertions(+), 13 deletions(-) diff --git a/src/components/incubator/EditingWorkspace.tsx b/src/components/incubator/EditingWorkspace.tsx index a6e5a83c02..4f01c92a6d 100644 --- a/src/components/incubator/EditingWorkspace.tsx +++ b/src/components/incubator/EditingWorkspace.tsx @@ -325,9 +325,7 @@ class AssessmentWorkspace extends React.Component - ) + body: }, { label: `Task ${questionId + 1}`, diff --git a/src/components/incubator/editingWorkspaceSideContent/HelpTab.tsx b/src/components/incubator/editingWorkspaceSideContent/HelpTab.tsx index 7863b6e9af..9ee97b33c8 100644 --- a/src/components/incubator/editingWorkspaceSideContent/HelpTab.tsx +++ b/src/components/incubator/editingWorkspaceSideContent/HelpTab.tsx @@ -13,16 +13,19 @@ export class HelpTab extends React.Component<{}, {}> { private helpTab = () => { return (
- Mission-specific tabs:
- Modify mission brief in the ission Briefing tab by clicking on the text
- Insert questions at the current question index or delete the current question in the Add/Delete Questions tab
- The Source version, library and globals for all questions in the mission can be modified in Global Deployment
-
- Question-specific tabs:
- Modify question description in Task tab
- Modify question template in Question template tab
- The Source version, library and globals for the current question can be modified in Global Deployment
- Modify grading in Grading tab
+ Mission-specific tabs:
+ Modify mission brief in the ission Briefing tab by clicking on the text
+ Insert questions at the current question index or delete the current question in the + Add/Delete Questions tab
+ The Source version, library and globals for all questions in the mission can be modified in + Global Deployment
+
+ Question-specific tabs:
+ Modify question description in Task tab
+ Modify question template in Question template tab
+ The Source version, library and globals for the current question can be modified in Global + Deployment
+ Modify grading in Grading tab
); }; From 079b843c96abfbe6ce415adc7bfd8462f2814a1a Mon Sep 17 00:00:00 2001 From: Kai Zhe Date: Sun, 24 Mar 2019 11:04:02 +0800 Subject: [PATCH 3/9] Changeded help description --- .../editingWorkspaceSideContent/HelpTab.tsx | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/src/components/incubator/editingWorkspaceSideContent/HelpTab.tsx b/src/components/incubator/editingWorkspaceSideContent/HelpTab.tsx index 9ee97b33c8..31d1e68e39 100644 --- a/src/components/incubator/editingWorkspaceSideContent/HelpTab.tsx +++ b/src/components/incubator/editingWorkspaceSideContent/HelpTab.tsx @@ -14,18 +14,15 @@ export class HelpTab extends React.Component<{}, {}> { return (
Mission-specific tabs:
- Modify mission brief in the ission Briefing tab by clicking on the text
- Insert questions at the current question index or delete the current question in the - Add/Delete Questions tab
- The Source version, library and globals for all questions in the mission can be modified in - Global Deployment
+ Mission Briefing: Modify mission brief by clicking on the text
+ Add/Delete Questions: Insert questions at the current question index or delete the current question
+ Global Deployment: Modify the Source version, library and globals for all questions in the mission

Question-specific tabs:
- Modify question description in Task tab
- Modify question template in Question template tab
- The Source version, library and globals for the current question can be modified in Global - Deployment
- Modify grading in Grading tab
+ Task: Modify question descriptio
+ Question Template: Modify question template in Question template tab
+ Local Deployment: Modify the Source version, library and globals for the current question
+ Grading: Modify grading
); }; From f09f97c9a20d165403415cfaec44c89277c475cf Mon Sep 17 00:00:00 2001 From: Ger Hean Date: Sun, 24 Mar 2019 12:15:06 +0800 Subject: [PATCH 4/9] fix import for firefox --- .../TextareaContent.tsx | 20 +++++++++---------- src/utils/xmlParser.ts | 19 +++++++++++++----- 2 files changed, 24 insertions(+), 15 deletions(-) diff --git a/src/components/incubator/editingWorkspaceSideContent/TextareaContent.tsx b/src/components/incubator/editingWorkspaceSideContent/TextareaContent.tsx index 27a0f9a4ea..613b477cb8 100644 --- a/src/components/incubator/editingWorkspaceSideContent/TextareaContent.tsx +++ b/src/components/incubator/editingWorkspaceSideContent/TextareaContent.tsx @@ -35,20 +35,20 @@ export class TextareaContent extends React.Component { } public render() { - const filler = 'Please enter value (if applicable)'; let display; if (this.state.isEditing) { - display =
{this.makeEditingTextarea()}
; + display = this.makeEditingTextarea(); } else { - let value = getValueFromPath(this.props.path, this.props.assessment) || filler; - value = value.match('^(\n| )*$') ? filler : value; - display = ( -
- {this.state.useRawValue ? value : } -
- ); + if (this.state.useRawValue) { + display = getValueFromPath(this.props.path, this.props.assessment); + } else { + const filler = 'Please enter value (if applicable)'; + let value = getValueFromPath(this.props.path, this.props.assessment) || ''; + value = value.match('^(\n| )*$') ? filler : value; + display = ; + } } - return display; + return
{display}
; } private saveEditAssessment = (e: any) => { diff --git a/src/utils/xmlParser.ts b/src/utils/xmlParser.ts index cbe827acee..54765a77ce 100644 --- a/src/utils/xmlParser.ts +++ b/src/utils/xmlParser.ts @@ -237,14 +237,23 @@ export const exportXml = () => { }; let xmlStr = builder.buildObject(xml); xmlStr = xmlStr.replace(/( )+/g, ''); - const element = document.createElement('a'); - const file = new Blob([xmlStr], { endings: 'native', type: 'text/xml;charset=UTF-8' }); - element.href = URL.createObjectURL(file); - element.download = title + '.xml'; - element.click(); + download(title + '.xml', xmlStr); } }; +const download = (filename: string, text: string) => { + const element = document.createElement('a'); + element.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(text)); + element.setAttribute('download', filename); + + element.style.display = 'none'; + document.body.appendChild(element); + + element.click(); + + document.body.removeChild(element); +}; + const exportLibrary = (library: Library) => { const deployment = { $: { From 0547d1fa076dd4eebd212127d7056f988562a166 Mon Sep 17 00:00:00 2001 From: Kai Zhe Date: Sun, 24 Mar 2019 13:39:30 +0800 Subject: [PATCH 5/9] max width for globals --- .../incubator/assessmentTemplates.ts | 2 +- .../DeploymentTab.tsx | 13 ++++++--- .../ManageQuestionTab.tsx | 6 ++--- .../QuestionTemplateTab.tsx | 27 +++++++++++++++++++ 4 files changed, 41 insertions(+), 7 deletions(-) diff --git a/src/components/incubator/assessmentTemplates.ts b/src/components/incubator/assessmentTemplates.ts index e8684115a3..10a77e5e8c 100644 --- a/src/components/incubator/assessmentTemplates.ts +++ b/src/components/incubator/assessmentTemplates.ts @@ -97,7 +97,7 @@ export const mcqTemplate = (): IMCQQuestion => { id: 2, library: emptyLibrary(), type: 'mcq', - solution: 1, + solution: 0, grader: { name: 'avenger', id: 1 diff --git a/src/components/incubator/editingWorkspaceSideContent/DeploymentTab.tsx b/src/components/incubator/editingWorkspaceSideContent/DeploymentTab.tsx index e762dc80da..26f99f96de 100644 --- a/src/components/incubator/editingWorkspaceSideContent/DeploymentTab.tsx +++ b/src/components/incubator/editingWorkspaceSideContent/DeploymentTab.tsx @@ -65,12 +65,19 @@ export class DeploymentTab extends React.Component{controlButton('Delete', IconNames.MINUS, this.handleSymbolDelete(i))} )); + const globals = deployment.globals.map((symbol, i) => ( - - {this.textareaContent(deploymentPath.concat(['globals', i, 0]))} + +
+ {this.textareaContent(deploymentPath.concat(['globals', i, 0]))} +
+ + +
+ {this.globalValueTextareaContent(i)} +
- {this.globalValueTextareaContent(i)} {controlButton('Delete', IconNames.MINUS, this.handleGlobalDelete(i))} diff --git a/src/components/incubator/editingWorkspaceSideContent/ManageQuestionTab.tsx b/src/components/incubator/editingWorkspaceSideContent/ManageQuestionTab.tsx index 15cf713006..5674e7f453 100644 --- a/src/components/incubator/editingWorkspaceSideContent/ManageQuestionTab.tsx +++ b/src/components/incubator/editingWorkspaceSideContent/ManageQuestionTab.tsx @@ -24,12 +24,12 @@ export class ManageQuestionTab extends React.Component { return (
{controlButton( - 'Make Programming Question', + 'Insert Programming Question', IconNames.FONT, this.makeQuestion(programmingTemplate) )} - {controlButton('Make MCQ Question', IconNames.CONFIRM, this.makeQuestion(mcqTemplate))} - {controlButton('Delete Question', IconNames.REMOVE, this.deleteQn)} + {controlButton('Insert MCQ Question', IconNames.CONFIRM, this.makeQuestion(mcqTemplate))} + {controlButton('Delete Current Question', IconNames.REMOVE, this.deleteQn)}
); }; diff --git a/src/components/incubator/editingWorkspaceSideContent/QuestionTemplateTab.tsx b/src/components/incubator/editingWorkspaceSideContent/QuestionTemplateTab.tsx index cfe5a3b9d8..2ad3f4ecd1 100644 --- a/src/components/incubator/editingWorkspaceSideContent/QuestionTemplateTab.tsx +++ b/src/components/incubator/editingWorkspaceSideContent/QuestionTemplateTab.tsx @@ -1,8 +1,10 @@ import { Card } from '@blueprintjs/core'; +import { IconNames } from '@blueprintjs/icons'; import * as React from 'react'; import AceEditor from 'react-ace'; import { IAssessment, IMCQQuestion } from '../../assessment/assessmentShape'; +import { controlButton } from '../../commons'; import { assignToPath, getValueFromPath } from './'; import TextareaContent from './TextareaContent'; @@ -78,12 +80,37 @@ export class QuestionTemplateTab extends React.Component { {mcqButton} Solution: {this.textareaContent(['questions', questionId, 'solution'], true, [0, 3])} + {controlButton('Add Option', IconNames.CONFIRM, this.addOption)} + {controlButton('Delete Option', IconNames.REMOVE, this.delOption)} ); }; + private addOption = () => { + const assessment = this.props.assessment; + const questionId = this.props.questionId; + const question = assessment!.questions[questionId] as IMCQQuestion; + const choices = question.choices.concat([{ + content: 'A', + hint: null + }]); + question.choices = choices; + assessment!.questions[questionId] = question; + this.props.updateAssessment(assessment); + } + + private delOption = () => { + const assessment = this.props.assessment; + const questionId = this.props.questionId; + const question = assessment!.questions[questionId] as IMCQQuestion; + const choices = question.choices.slice(0, question.choices.length - 1); + question.choices = choices; + assessment!.questions[questionId] = question; + this.props.updateAssessment(assessment); + } + private textareaContent = ( path: Array, isNumber: boolean = false, From 19044bbbe13d8b3f6623592e1e006edf7575e5c2 Mon Sep 17 00:00:00 2001 From: Kai Zhe Date: Sun, 24 Mar 2019 14:08:58 +0800 Subject: [PATCH 6/9] symbols aligned --- .../incubator/editingWorkspaceSideContent/DeploymentTab.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/components/incubator/editingWorkspaceSideContent/DeploymentTab.tsx b/src/components/incubator/editingWorkspaceSideContent/DeploymentTab.tsx index 26f99f96de..ec070c99c3 100644 --- a/src/components/incubator/editingWorkspaceSideContent/DeploymentTab.tsx +++ b/src/components/incubator/editingWorkspaceSideContent/DeploymentTab.tsx @@ -61,7 +61,9 @@ export class DeploymentTab extends React.Component ( - {this.textareaContent(deploymentPath.concat(['external', 'symbols', i]))} + + {this.textareaContent(deploymentPath.concat(['external', 'symbols', i]))} + {controlButton('Delete', IconNames.MINUS, this.handleSymbolDelete(i))} )); From 0cf76f4f96bca1389dc736099b57d1b22e113325 Mon Sep 17 00:00:00 2001 From: Ger Hean Date: Sun, 24 Mar 2019 15:44:31 +0800 Subject: [PATCH 7/9] increase abstraction removed numberRange from textArea Reduced save frequency of solutionTemplate --- .../GradingTab.tsx | 10 +- .../QuestionTemplateTab.tsx | 105 +++++++++++++----- .../TextareaContent.tsx | 8 +- .../editingWorkspaceSideContent/index.tsx | 14 +++ 4 files changed, 99 insertions(+), 38 deletions(-) diff --git a/src/components/incubator/editingWorkspaceSideContent/GradingTab.tsx b/src/components/incubator/editingWorkspaceSideContent/GradingTab.tsx index 04d1ad0140..9f445ef945 100644 --- a/src/components/incubator/editingWorkspaceSideContent/GradingTab.tsx +++ b/src/components/incubator/editingWorkspaceSideContent/GradingTab.tsx @@ -1,6 +1,7 @@ import * as React from 'react'; import { IAssessment } from '../../assessment/assessmentShape'; +import { limitNumberRange } from './'; import TextareaContent from './TextareaContent'; interface IProps { @@ -18,12 +19,13 @@ export class GradingTab extends React.Component { return this.gradingTab(); } - private textareaContent = (path: Array, isNumber: boolean = false) => { + private textareaContent = (path: Array) => { return ( ); @@ -32,10 +34,10 @@ export class GradingTab extends React.Component { private gradingTab = () => (
Max Grade: - {this.textareaContent(this.props.path.concat(['maxGrade']), true)} + {this.textareaContent(this.props.path.concat(['maxGrade']))}
Max Xp: - {this.textareaContent(this.props.path.concat(['maxXp']), true)} + {this.textareaContent(this.props.path.concat(['maxXp']))}
); } diff --git a/src/components/incubator/editingWorkspaceSideContent/QuestionTemplateTab.tsx b/src/components/incubator/editingWorkspaceSideContent/QuestionTemplateTab.tsx index cfe5a3b9d8..cb3bae3f11 100644 --- a/src/components/incubator/editingWorkspaceSideContent/QuestionTemplateTab.tsx +++ b/src/components/incubator/editingWorkspaceSideContent/QuestionTemplateTab.tsx @@ -3,7 +3,7 @@ import * as React from 'react'; import AceEditor from 'react-ace'; import { IAssessment, IMCQQuestion } from '../../assessment/assessmentShape'; -import { assignToPath, getValueFromPath } from './'; +import { assignToPath, getValueFromPath, limitNumberRange } from './'; import TextareaContent from './TextareaContent'; interface IProps { @@ -12,9 +12,18 @@ interface IProps { updateAssessment: (assessment: IAssessment) => void; } -export class QuestionTemplateTab extends React.Component { +interface IState { + templateValue: string; + templateFocused: boolean; +} + +export class QuestionTemplateTab extends React.Component { public constructor(props: IProps) { super(props); + this.state = { + templateValue: '', + templateFocused: false + }; } public render() { @@ -34,30 +43,60 @@ export class QuestionTemplateTab extends React.Component { const path = ['questions', this.props.questionId, 'answer']; const handleTemplateChange = (newCode: string) => { - const assessmentVal = this.props.assessment; - assignToPath(path, newCode, assessmentVal); - this.props.updateAssessment(assessmentVal); + this.setState({ + templateValue: newCode + }); }; + const value = this.state.templateFocused + ? this.state.templateValue + : getValueFromPath(path, this.props.assessment); + const display = ( - +
+ +
); return display; }; + private focusEditor = (path: Array) => (e: any): void => { + if (!this.state.templateFocused) { + this.setState({ + templateValue: getValueFromPath(path, this.props.assessment), + templateFocused: true + }); + } + }; + + private unFocusEditor = (path: Array) => (e: any): void => { + if (this.state.templateFocused) { + const value = getValueFromPath(path, this.props.assessment); + if (value !== this.state.templateValue) { + const assessmentVal = this.props.assessment; + assignToPath(path, this.state.templateValue, assessmentVal); + this.props.updateAssessment(assessmentVal); + } + this.setState({ + templateValue: '', + templateFocused: false + }); + } + }; + private mcqTab = () => { const questionId = this.props.questionId; const question = this.props.assessment!.questions[questionId] as IMCQQuestion; @@ -87,17 +126,27 @@ export class QuestionTemplateTab extends React.Component { private textareaContent = ( path: Array, isNumber: boolean = false, - numberRange: number[] = [0] + range: number[] = [0] ) => { - return ( - - ); + if (isNumber) { + return ( + + ); + } else { + return ( + + ); + } }; } diff --git a/src/components/incubator/editingWorkspaceSideContent/TextareaContent.tsx b/src/components/incubator/editingWorkspaceSideContent/TextareaContent.tsx index 613b477cb8..14086f182e 100644 --- a/src/components/incubator/editingWorkspaceSideContent/TextareaContent.tsx +++ b/src/components/incubator/editingWorkspaceSideContent/TextareaContent.tsx @@ -8,7 +8,6 @@ import { assignToPath, getValueFromPath } from './'; interface IProps { assessment: IAssessment; isNumber?: boolean; - numberRange?: number[]; path: Array; useRawValue?: boolean; processResults?: (newVal: string | number) => string | number; @@ -54,12 +53,9 @@ export class TextareaContent extends React.Component { private saveEditAssessment = (e: any) => { let fieldValue: number | string; if (this.state.isNumber) { - const range = this.props.numberRange || [0]; fieldValue = parseInt(this.state.fieldValue, 10); - if (isNaN(fieldValue) || fieldValue < range[0]) { - fieldValue = range[0]; - } else if (range.length > 1 && fieldValue > range[1]) { - fieldValue = range[1]; + if (isNaN(fieldValue)) { + fieldValue = getValueFromPath(this.props.path, this.props.assessment); } } else { fieldValue = this.state.fieldValue; diff --git a/src/components/incubator/editingWorkspaceSideContent/index.tsx b/src/components/incubator/editingWorkspaceSideContent/index.tsx index d89c4bbc5f..6d73029c78 100644 --- a/src/components/incubator/editingWorkspaceSideContent/index.tsx +++ b/src/components/incubator/editingWorkspaceSideContent/index.tsx @@ -20,3 +20,17 @@ export const assignToPath = (path: Array, value: any, obj: any) } obj[path[i]] = value; }; + +export const limitNumberRange = (min: number | null = 0, max: number | null = null) => ( + value: number +): number => { + let result; + if (min !== null && value < min) { + result = min; + } else if (max !== null && value > max) { + result = max; + } else { + result = value; + } + return result; +}; From 01ffb72d612321ebaf61d1dea2af27cd535116fd Mon Sep 17 00:00:00 2001 From: Ger Hean Date: Sun, 24 Mar 2019 16:18:49 +0800 Subject: [PATCH 8/9] manage questions give warning --- src/components/incubator/EditingWorkspace.tsx | 1 + .../ManageQuestionTab.tsx | 77 +++++++++++++++++-- 2 files changed, 73 insertions(+), 5 deletions(-) diff --git a/src/components/incubator/EditingWorkspace.tsx b/src/components/incubator/EditingWorkspace.tsx index 1e790de1d9..8b59643872 100644 --- a/src/components/incubator/EditingWorkspace.tsx +++ b/src/components/incubator/EditingWorkspace.tsx @@ -393,6 +393,7 @@ class AssessmentWorkspace extends React.Component diff --git a/src/components/incubator/editingWorkspaceSideContent/ManageQuestionTab.tsx b/src/components/incubator/editingWorkspaceSideContent/ManageQuestionTab.tsx index 15cf713006..f374749540 100644 --- a/src/components/incubator/editingWorkspaceSideContent/ManageQuestionTab.tsx +++ b/src/components/incubator/editingWorkspaceSideContent/ManageQuestionTab.tsx @@ -1,23 +1,40 @@ +import { ButtonGroup, Classes, Dialog, Intent } from '@blueprintjs/core'; import { IconNames } from '@blueprintjs/icons'; import * as React from 'react'; import { IAssessment } from '../../assessment/assessmentShape'; import { controlButton } from '../../commons'; +import Markdown from '../../commons/Markdown'; import { mcqTemplate, programmingTemplate } from '../../incubator/assessmentTemplates'; interface IProps { assessment: IAssessment; + hasUnsavedChanges: boolean; questionId: number; updateAssessment: (assessment: IAssessment) => void; } -export class ManageQuestionTab extends React.Component { +interface IState { + showSaveOverlay: boolean; + modifyAssessment: () => void; +} + +export class ManageQuestionTab extends React.Component { public constructor(props: IProps) { super(props); + this.state = { + showSaveOverlay: false, + modifyAssessment: () => {} + }; } public render() { - return this.manageQuestionTab(); + return ( +
+ {this.confirmSaveOverlay()} + {this.manageQuestionTab()} +
+ ); } private manageQuestionTab = () => { @@ -26,10 +43,14 @@ export class ManageQuestionTab extends React.Component { {controlButton( 'Make Programming Question', IconNames.FONT, - this.makeQuestion(programmingTemplate) + this.confirmSave(this.makeQuestion(programmingTemplate)) + )} + {controlButton( + 'Make MCQ Question', + IconNames.CONFIRM, + this.confirmSave(this.makeQuestion(mcqTemplate)) )} - {controlButton('Make MCQ Question', IconNames.CONFIRM, this.makeQuestion(mcqTemplate))} - {controlButton('Delete Question', IconNames.REMOVE, this.deleteQn)} + {controlButton('Delete Question', IconNames.REMOVE, this.confirmSave(this.deleteQn))} ); }; @@ -56,6 +77,52 @@ export class ManageQuestionTab extends React.Component { assessment.questions = questions; this.props.updateAssessment(assessment); }; + + private confirmSave = (modifyAssessment: () => void) => () => { + if (this.props.hasUnsavedChanges) { + this.setState({ + showSaveOverlay: true, + modifyAssessment + }); + } else { + modifyAssessment(); + } + }; + + /** + * Asks to save work. + */ + private confirmSaveOverlay = () => ( + +
+ +
+
+ + {controlButton('Cancel', null, () => this.setState({ showSaveOverlay: false }), { + minimal: false + })} + {controlButton( + 'Confirm', + null, + () => { + this.state.modifyAssessment(); + this.setState({ + showSaveOverlay: false + }); + }, + { minimal: false, intent: Intent.DANGER } + )} + +
+
+ ); } export default ManageQuestionTab; From 113829440245c64732d32fa37e9689ad8e8042c4 Mon Sep 17 00:00:00 2001 From: Kai Zhe Date: Sun, 24 Mar 2019 16:28:37 +0800 Subject: [PATCH 9/9] UI change for deployment tab --- .../DeploymentTab.tsx | 74 ++++++++++++++----- .../QuestionTemplateTab.tsx | 16 ++-- 2 files changed, 64 insertions(+), 26 deletions(-) diff --git a/src/components/incubator/editingWorkspaceSideContent/DeploymentTab.tsx b/src/components/incubator/editingWorkspaceSideContent/DeploymentTab.tsx index ec070c99c3..7ed4aeff37 100644 --- a/src/components/incubator/editingWorkspaceSideContent/DeploymentTab.tsx +++ b/src/components/incubator/editingWorkspaceSideContent/DeploymentTab.tsx @@ -7,6 +7,7 @@ import { sourceChapters } from '../../../reducers/states'; import { ExternalLibraryName, IAssessment, Library } from '../../assessment/assessmentShape'; import { controlButton } from '../../commons'; +import SideContent from '../../workspace/side-content'; import { emptyLibrary } from '../assessmentTemplates'; import { assignToPath, getValueFromPath } from './'; import TextareaContent from './TextareaContent'; @@ -30,10 +31,14 @@ interface IExternal { symbols: string[]; } -export class DeploymentTab extends React.Component { +export class DeploymentTab extends React.Component< + IProps, + { activeTab: number; deploymentEnabled: boolean } +> { public constructor(props: IProps) { super(props); this.state = { + activeTab: 0, deploymentEnabled: false }; } @@ -58,10 +63,10 @@ export class DeploymentTab extends React.Component { const deploymentPath = this.props.pathToLibrary; const deployment = getValueFromPath(deploymentPath, this.props.assessment) as Library; - const deploymentDisp = this.props.isGlobalDeployment ? 'Global Deployment' : 'Local Deployment'; + // const deploymentDisp = this.props.isGlobalDeployment ? 'Global Deployment' : 'Local Deployment'; const symbols = deployment.external.symbols.map((symbol, i) => ( - + {this.textareaContent(deploymentPath.concat(['external', 'symbols', i]))} {controlButton('Delete', IconNames.MINUS, this.handleSymbolDelete(i))} @@ -70,13 +75,13 @@ export class DeploymentTab extends React.Component ( - -
+ +
{this.textareaContent(deploymentPath.concat(['globals', i, 0]))}
- -
+ +
{this.globalValueTextareaContent(i)}
@@ -90,16 +95,8 @@ export class DeploymentTab extends React.Component - {deploymentDisp} {resetLibrary} -
-
- Interpreter: -
- {chapterSelect(deployment.chapter, this.handleChapterSelect)} -
-
+ const symbolsFragment = ( + External Library:
{externalSelect(deployment.external.name, this.handleExternalSelect!)} @@ -109,16 +106,55 @@ export class DeploymentTab extends React.Component {symbols}
{controlButton('New Symbol', IconNames.PLUS, this.handleNewSymbol)} -
-
+
+ ); + + const globalsFragment = ( +
Globals:

{globals}
{controlButton('New Global', IconNames.PLUS, this.handleNewGlobal)} +
+ ); + + const tabs = [ + { + label: `Library`, + icon: IconNames.BOOK, + body: symbolsFragment + }, + { + label: `Globals`, + icon: IconNames.GLOBE, + body: globalsFragment + } + ]; + + return ( +
+ {/* {deploymentDisp} +
*/} + {resetLibrary} +
+ Interpreter: +
+ {chapterSelect(deployment.chapter, this.handleChapterSelect)} +
); }; + private handleChangeActiveTab = (tab: number) => { + this.setState({ + activeTab: tab + }); + }; + private textareaContent = (path: Array) => { return ( { const assessment = this.props.assessment; const questionId = this.props.questionId; const question = assessment!.questions[questionId] as IMCQQuestion; - const choices = question.choices.concat([{ - content: 'A', - hint: null - }]); + const choices = question.choices.concat([ + { + content: 'A', + hint: null + } + ]); question.choices = choices; assessment!.questions[questionId] = question; this.props.updateAssessment(assessment); - } - + }; + private delOption = () => { const assessment = this.props.assessment; const questionId = this.props.questionId; @@ -109,7 +111,7 @@ export class QuestionTemplateTab extends React.Component { question.choices = choices; assessment!.questions[questionId] = question; this.props.updateAssessment(assessment); - } + }; private textareaContent = ( path: Array,