Skip to content

Commit 59b1a44

Browse files
committed
Merge branch 'pr/486' into mission-editing
1 parent 74e3f10 commit 59b1a44

File tree

6 files changed

+293
-192
lines changed

6 files changed

+293
-192
lines changed

src/components/incubator/EditingWorkspace.tsx

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ import { ControlBarProps } from '../workspace/ControlBar';
1919
import { SideContentProps } from '../workspace/side-content';
2020
import ToneMatrix from '../workspace/side-content/ToneMatrix';
2121
import {
22-
GlobalDeploymentTab,
22+
DeploymentTab,
2323
GradingTab,
2424
ManageQuestionTab,
2525
QuestionTemplateTab,
@@ -293,11 +293,25 @@ class AssessmentWorkspace extends React.Component<AssessmentWorkspaceProps, ISta
293293
},
294294
{
295295
label: `Manage Global Deployment`,
296-
icon: IconNames.TAG,
296+
icon: IconNames.GLOBE,
297297
body: (
298-
<GlobalDeploymentTab
298+
<DeploymentTab
299299
assessment={assessment}
300+
pathToLibrary={['globalDeployment']}
300301
updateAssessment={this.updateEditAssessmentState}
302+
isGlobalDeployment={true}
303+
/>
304+
)
305+
},
306+
{
307+
label: `Manage Local Deployment`,
308+
icon: IconNames.HOME,
309+
body: (
310+
<DeploymentTab
311+
assessment={assessment}
312+
pathToLibrary={['questions', questionId, 'library']}
313+
updateAssessment={this.updateEditAssessmentState}
314+
isGlobalDeployment={false}
301315
/>
302316
)
303317
}

src/components/incubator/assessmentTemplates.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import {
99
} from '../../components/assessment/assessmentShape';
1010
import { mock2DRuneLibrary } from '../../mocks/assessmentAPI';
1111

12-
const emptyLibrary: Library = {
12+
export const emptyLibrary: Library = {
1313
chapter: -1,
1414
external: {
1515
name: 'NONE',
Lines changed: 268 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,268 @@
1+
import { Button, MenuItem, Switch } from '@blueprintjs/core';
2+
import { IconNames } from '@blueprintjs/icons';
3+
import { ItemRenderer, Select } from '@blueprintjs/select';
4+
import * as React from 'react';
5+
import { externalLibraries } from '../../../reducers/externalLibraries';
6+
import { sourceChapters } from '../../../reducers/states';
7+
8+
import { ExternalLibraryName, IAssessment, Library } from '../../assessment/assessmentShape';
9+
import { controlButton } from '../../commons';
10+
import { emptyLibrary } from '../assessmentTemplates';
11+
import { assignToPath, getValueFromPath } from './';
12+
import TextareaContent from './TextareaContent';
13+
14+
interface IProps {
15+
assessment: IAssessment;
16+
pathToLibrary: Array<string | number>;
17+
updateAssessment: (assessment: IAssessment) => void;
18+
isGlobalDeployment: boolean;
19+
}
20+
21+
interface IChapter {
22+
chapter: number;
23+
displayName: string;
24+
}
25+
26+
interface IExternal {
27+
key: number;
28+
name: ExternalLibraryName;
29+
symbols: string[];
30+
}
31+
32+
export class DeploymentTab extends React.Component<IProps, { deploymentEnabled: boolean }> {
33+
public constructor(props: IProps) {
34+
super(props);
35+
this.state = {
36+
deploymentEnabled: false
37+
};
38+
}
39+
40+
public render() {
41+
if (this.props.isGlobalDeployment) {
42+
return this.deploymentTab();
43+
} else {
44+
return (
45+
<div>
46+
<Switch
47+
checked={this.state.deploymentEnabled}
48+
label="Enable Local Deployment"
49+
onChange={this.handleSwitchDeployment}
50+
/>
51+
{this.state.deploymentEnabled ? this.deploymentTab() : null}
52+
</div>
53+
);
54+
}
55+
}
56+
57+
private deploymentTab = () => {
58+
const deploymentPath = this.props.pathToLibrary;
59+
const deployment = getValueFromPath(deploymentPath, this.props.assessment) as Library;
60+
const deploymentDisp = this.props.isGlobalDeployment ? 'Global Deployment' : 'Local Deployment';
61+
const symbols = deployment.external.symbols.map((symbol, i) => (
62+
<tr key={i}>
63+
<td>{this.textareaContent(deploymentPath.concat(['external', 'symbols', i]))}</td>
64+
<td>{controlButton('Delete', IconNames.MINUS, this.handleSymbolDelete(i))}</td>
65+
</tr>
66+
));
67+
const globals = deployment.globals.map((symbol, i) => (
68+
<tr key={i}>
69+
<td className="col-xs-3">
70+
{this.textareaContent(deploymentPath.concat(['globals', i, 0]))}
71+
</td>
72+
<td className="col-xs-7">{this.globalValueTextareaContent(i)}</td>
73+
<td className="col-xs-2">
74+
{controlButton('Delete', IconNames.MINUS, this.handleGlobalDelete(i))}
75+
</td>
76+
</tr>
77+
));
78+
79+
return (
80+
<div>
81+
{deploymentDisp}
82+
<br />
83+
<br />
84+
Interpreter:
85+
<br />
86+
{chapterSelect(deployment.chapter, this.handleChapterSelect)}
87+
<br />
88+
<br />
89+
External Library:
90+
<br />
91+
{externalSelect(deployment.external.name, this.handleExternalSelect!)}
92+
<br />
93+
<br />
94+
<div>Symbols:</div>
95+
<br />
96+
<table style={{ width: '100%' }}>{symbols}</table>
97+
{controlButton('New Symbol', IconNames.PLUS, this.handleNewSymbol)}
98+
<br />
99+
<br />
100+
<div>Globals:</div>
101+
<br />
102+
<table style={{ width: '100%' }}>{globals}</table>
103+
{controlButton('New Global', IconNames.PLUS, this.handleNewGlobal)}
104+
</div>
105+
);
106+
};
107+
108+
private textareaContent = (path: Array<string | number>) => {
109+
return (
110+
<TextareaContent
111+
assessment={this.props.assessment}
112+
path={path}
113+
processResults={removeSpaces}
114+
updateAssessment={this.props.updateAssessment}
115+
useRawValue={true}
116+
/>
117+
);
118+
};
119+
120+
private globalValueTextareaContent = (i: number) => {
121+
const pathVal = this.props.pathToLibrary.concat(['globals', i, 2]);
122+
return (
123+
<TextareaContent
124+
assessment={this.props.assessment}
125+
path={pathVal}
126+
updateAssessment={this.handleGlobalValueUpdate(i)}
127+
useRawValue={true}
128+
/>
129+
);
130+
};
131+
132+
private handleGlobalValueUpdate = (i: number) => (assessment: IAssessment) => {
133+
const deployment = getValueFromPath(this.props.pathToLibrary, this.props.assessment) as Library;
134+
const global = deployment.globals[i];
135+
try {
136+
global[1] = altEval(global[2]!);
137+
this.props.updateAssessment(assessment);
138+
} catch (e) {
139+
global[2] = 'Invalid Expression';
140+
}
141+
};
142+
143+
private handleSymbolDelete = (index: number) => () => {
144+
const assessment = this.props.assessment;
145+
const deployment = getValueFromPath(this.props.pathToLibrary, assessment) as Library;
146+
const symbols = deployment.external.symbols;
147+
symbols.splice(index, 1);
148+
this.props.updateAssessment(assessment);
149+
};
150+
151+
private handleNewSymbol = () => {
152+
const assessment = this.props.assessment;
153+
const deployment = getValueFromPath(this.props.pathToLibrary, assessment) as Library;
154+
const symbols = deployment.external.symbols;
155+
symbols.push('new symbol');
156+
this.props.updateAssessment(assessment);
157+
};
158+
159+
private handleGlobalDelete = (index: number) => () => {
160+
const assessment = this.props.assessment;
161+
const deployment = getValueFromPath(this.props.pathToLibrary, assessment) as Library;
162+
deployment.globals.splice(index, 1);
163+
this.props.updateAssessment(assessment);
164+
};
165+
166+
private handleNewGlobal = () => {
167+
const assessment = this.props.assessment;
168+
const deployment = getValueFromPath(this.props.pathToLibrary, assessment) as Library;
169+
deployment.globals.push(['new_global', null, 'null']);
170+
this.props.updateAssessment(assessment);
171+
};
172+
173+
private handleChapterSelect = (i: IChapter, e: React.ChangeEvent<HTMLSelectElement>) => {
174+
const assessment = this.props.assessment;
175+
const deployment = getValueFromPath(this.props.pathToLibrary, assessment) as Library;
176+
deployment.chapter = i.chapter;
177+
this.props.updateAssessment(assessment);
178+
};
179+
180+
private handleExternalSelect = (i: IExternal, e: React.ChangeEvent<HTMLSelectElement>) => {
181+
const assessment = this.props.assessment;
182+
const deployment = getValueFromPath(this.props.pathToLibrary, assessment) as Library;
183+
deployment.external.name = i.name;
184+
deployment.external.symbols = JSON.parse(JSON.stringify(externalLibraries.get(i.name)!));
185+
this.props.updateAssessment(assessment);
186+
};
187+
188+
private handleSwitchDeployment = () => {
189+
const assessment = this.props.assessment;
190+
if (this.state.deploymentEnabled) {
191+
assignToPath(this.props.pathToLibrary, JSON.parse(JSON.stringify(emptyLibrary)), assessment);
192+
} else {
193+
assignToPath(this.props.pathToLibrary.concat(['chapter']), 1, assessment);
194+
}
195+
this.setState({
196+
deploymentEnabled: !this.state.deploymentEnabled
197+
});
198+
this.props.updateAssessment(assessment);
199+
};
200+
}
201+
202+
const removeSpaces = (str: string) => {
203+
return str.replace(/\s+/g, '');
204+
};
205+
206+
function styliseChapter(chap: number) {
207+
return `Source \xa7${chap}`;
208+
}
209+
210+
const chapters = sourceChapters.map(chap => ({ displayName: styliseChapter(chap), chapter: chap }));
211+
212+
const chapterSelect = (
213+
currentChap: number,
214+
handleSelect = (i: IChapter, e: React.ChangeEvent<HTMLSelectElement>) => {}
215+
) => (
216+
<ChapterSelectComponent
217+
className="pt-minimal"
218+
items={chapters}
219+
onItemSelect={handleSelect}
220+
itemRenderer={chapterRenderer}
221+
filterable={false}
222+
>
223+
<Button
224+
className="pt-minimal"
225+
text={styliseChapter(currentChap)}
226+
rightIcon="double-caret-vertical"
227+
/>
228+
</ChapterSelectComponent>
229+
);
230+
231+
const altEval = (str: string): any => {
232+
return Function('"use strict";return (' + str + ')')();
233+
};
234+
235+
const ChapterSelectComponent = Select.ofType<IChapter>();
236+
237+
const chapterRenderer: ItemRenderer<IChapter> = (chap, { handleClick, modifiers, query }) => (
238+
<MenuItem active={false} key={chap.chapter} onClick={handleClick} text={chap.displayName} />
239+
);
240+
241+
const iExternals = Array.from(externalLibraries.entries()).map((entry, index) => ({
242+
name: entry[0] as ExternalLibraryName,
243+
key: index,
244+
symbols: entry[1]
245+
}));
246+
247+
const externalSelect = (
248+
currentExternal: string,
249+
handleSelect: (i: IExternal, e: React.ChangeEvent<HTMLSelectElement>) => void
250+
) => (
251+
<ExternalSelectComponent
252+
className="pt-minimal"
253+
items={iExternals}
254+
onItemSelect={handleSelect}
255+
itemRenderer={externalRenderer}
256+
filterable={false}
257+
>
258+
<Button className="pt-minimal" text={currentExternal} rightIcon="double-caret-vertical" />
259+
</ExternalSelectComponent>
260+
);
261+
262+
const ExternalSelectComponent = Select.ofType<IExternal>();
263+
264+
const externalRenderer: ItemRenderer<IExternal> = (external, { handleClick, modifiers, query }) => (
265+
<MenuItem active={false} key={external.key} onClick={handleClick} text={external.name} />
266+
);
267+
268+
export default DeploymentTab;

0 commit comments

Comments
 (0)