Skip to content

Commit 27ed739

Browse files
committed
Merge remote-tracking branch 'origin/master' into conductor/persistent-ff
# Conflicts: # package.json # src/commons/controlBar/ControlBarChapterSelect.tsx # src/commons/featureFlags/publicFlags.ts # src/commons/navigationBar/subcomponents/NavigationBarLangSelectButton.tsx # yarn.lock
2 parents 9e2c058 + 731a12c commit 27ed739

File tree

18 files changed

+2530
-183
lines changed

18 files changed

+2530
-183
lines changed

public/evaluators/pie-slang/pie.js

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

public/evaluators/py-slang/pyscript.js

Lines changed: 1374 additions & 0 deletions
Large diffs are not rendered by default.

public/evaluators/py-slang/standard.js

Lines changed: 722 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/commons/application/ApplicationTypes.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { Chapter, Language, type SourceError, type Value, Variant } from 'js-sla
22

33
import type { AchievementState } from '../../features/achievement/AchievementTypes';
44
import type { DashboardState } from '../../features/dashboard/DashboardTypes';
5+
import type { LanguageDirectoryState } from '../../features/languageDirectory/LanguageDirectoryTypes';
56
import type { LeaderboardState } from '../../features/leaderboard/LeaderboardTypes';
67
import type { PlaygroundState } from '../../features/playground/PlaygroundTypes';
78
import { PlaybackStatus, RecordingStatus } from '../../features/sourceRecorder/SourceRecorderTypes';
@@ -38,6 +39,7 @@ export type OverallState = {
3839
readonly fileSystem: FileSystemState;
3940
readonly sideContent: SideContentManagerState;
4041
readonly vscode: VscodeState;
42+
readonly languageDirectory: LanguageDirectoryState;
4143
};
4244

4345
export type Story = {
@@ -621,6 +623,12 @@ export const defaultVscode: VscodeState = {
621623
isVscode: false
622624
};
623625

626+
export const defaultLanguageDirectory: LanguageDirectoryState = {
627+
selectedLanguageId: null,
628+
selectedEvaluatorId: null,
629+
languages: []
630+
};
631+
624632
export const defaultState: OverallState = {
625633
router: defaultRouter,
626634
achievement: defaultAchievement,
@@ -633,5 +641,6 @@ export const defaultState: OverallState = {
633641
featureFlags: defaultFeatureFlags,
634642
fileSystem: defaultFileSystem,
635643
sideContent: defaultSideContentManager,
636-
vscode: defaultVscode
644+
vscode: defaultVscode,
645+
languageDirectory: defaultLanguageDirectory
637646
};

src/commons/application/reducers/RootReducer.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,15 @@
1-
import { combineReducers, Reducer } from '@reduxjs/toolkit';
2-
import { SourceActionType } from 'src/commons/utils/ActionsHelper';
1+
import { combineReducers, type Reducer } from '@reduxjs/toolkit';
32

43
import { FeatureFlagsReducer as featureFlags } from '../../..//commons/featureFlags';
54
import { AchievementReducer as achievement } from '../../../features/achievement/AchievementReducer';
65
import { DashboardReducer as dashboard } from '../../../features/dashboard/DashboardReducer';
6+
import { LanguageDirectoryReducer as languageDirectory } from '../../../features/languageDirectory/LanguageDirectoryReducer';
77
import { LeaderboardReducer as leaderboard } from '../../../features/leaderboard/LeaderboardReducer';
88
import { PlaygroundReducer as playground } from '../../../features/playground/PlaygroundReducer';
99
import { StoriesReducer as stories } from '../../../features/stories/StoriesReducer';
1010
import { FileSystemReducer as fileSystem } from '../../fileSystem/FileSystemReducer';
1111
import { SideContentReducer as sideContent } from '../../sideContent/SideContentReducer';
12+
import type { SourceActionType } from '../../utils/ActionsHelper';
1213
import { WorkspaceReducer as workspaces } from '../../workspace/WorkspaceReducer';
1314
import { OverallState } from '../ApplicationTypes';
1415
import { RouterReducer as router } from './CommonsReducer';
@@ -27,7 +28,8 @@ const rootReducer: Reducer<OverallState, SourceActionType> = combineReducers({
2728
featureFlags,
2829
fileSystem,
2930
sideContent,
30-
vscode
31+
vscode,
32+
languageDirectory
3133
});
3234

3335
export default rootReducer;
Lines changed: 55 additions & 114 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,17 @@
1-
import { Button, Menu, MenuItem, Tooltip } from '@blueprintjs/core';
1+
import { Button, Menu, MenuItem } from '@blueprintjs/core';
22
import { IconNames } from '@blueprintjs/icons';
33
import { ItemListRenderer, ItemRenderer, Select } from '@blueprintjs/select';
44
import { Chapter, Variant } from 'js-slang/dist/types';
5-
import React from 'react';
5+
import React, { useEffect } from 'react';
66
import { useDispatch } from 'react-redux';
7-
import { playgroundConductorEvaluator } from 'src/features/playground/PlaygroundActions';
87

9-
import { flagConductorEnable } from '../../features/conductor/flagConductorEnable';
10-
import {
11-
fullJSLanguage,
12-
fullTSLanguage,
13-
htmlLanguage,
14-
javaLanguages,
15-
pyLanguages,
16-
SALanguage,
17-
schemeLanguages,
18-
sourceLanguages,
19-
styliseSublanguage
20-
} from '../application/ApplicationTypes';
21-
import { IEvaluatorDefinition } from '../directory/language';
8+
import { flagLanguageDirectoryEnable } from '../../features/languageDirectory/flagLanguageDirectory';
9+
import LanguageDirectoryActions from '../../features/languageDirectory/LanguageDirectoryActions';
10+
import type { IEvaluatorDefinition } from '../../features/languageDirectory/LanguageDirectoryTypes';
11+
import { SALanguage } from '../application/ApplicationTypes';
2212
import { useFeature } from '../featureFlags/useFeature';
23-
import Constants from '../utils/Constants';
2413
import { useTypedSelector } from '../utils/Hooks';
14+
import { LegacyControlBarChapterSelect } from './LegacyControlBarChapterSelect';
2515

2616
type ControlBarChapterSelectProps = DispatchProps & StateProps;
2717

@@ -36,126 +26,77 @@ type StateProps = {
3626
disabled?: boolean;
3727
};
3828

39-
const chapterListRenderer: ItemListRenderer<SALanguage> = ({
40-
itemsParentRef,
41-
renderItem,
42-
items
43-
}) => {
44-
const defaultChoices = items.filter(({ variant }) => variant === Variant.DEFAULT);
45-
const variantChoices = items.filter(({ variant }) => variant !== Variant.DEFAULT);
46-
47-
return (
48-
<Menu ulRef={itemsParentRef} style={{ display: 'flex', flexDirection: 'column' }}>
49-
{defaultChoices.map(renderItem)}
50-
{variantChoices.length > 0 && (
51-
<MenuItem key="variant-menu" text="Variants" icon="cog">
52-
{variantChoices.map(renderItem)}
53-
</MenuItem>
54-
)}
55-
</Menu>
56-
);
57-
};
58-
59-
const chapterRenderer: (isFolderModeEnabled: boolean) => ItemRenderer<SALanguage> =
60-
(isFolderModeEnabled: boolean) =>
61-
(lang, { handleClick }) => {
62-
const isDisabled = isFolderModeEnabled && lang.chapter === Chapter.SOURCE_1;
63-
const tooltipContent = isDisabled
64-
? 'Folder mode makes use of lists which are not available in Source 1. To switch to Source 1, disable Folder mode.'
65-
: undefined;
66-
return (
67-
<Tooltip
68-
key={lang.displayName}
69-
content={tooltipContent}
70-
disabled={tooltipContent === undefined}
71-
>
72-
<MenuItem onClick={handleClick} text={lang.displayName} disabled={isDisabled} />
73-
</Tooltip>
74-
);
75-
};
76-
77-
const evaluatorListRenderer: ItemListRenderer<IEvaluatorDefinition> = ({
78-
itemsParentRef,
79-
renderItem,
80-
items
81-
}) => {
82-
return (
83-
<Menu ulRef={itemsParentRef} style={{ display: 'flex', flexDirection: 'column' }}>
84-
{items.map(renderItem)}
85-
</Menu>
86-
);
87-
};
88-
89-
const evaluatorRenderer: ItemRenderer<IEvaluatorDefinition> = (evaluator, { handleClick }) => {
90-
return <MenuItem onClick={handleClick} text={evaluator.name} />;
91-
};
92-
93-
const ChapterSelectComponent = Select.ofType<SALanguage>();
94-
const EvaluatorSelectComponent = Select<IEvaluatorDefinition>;
95-
9629
export const ControlBarChapterSelect: React.FC<ControlBarChapterSelectProps> = ({
9730
isFolderModeEnabled,
9831
sourceChapter,
9932
sourceVariant,
10033
handleChapterSelect = () => {},
10134
disabled = false
10235
}) => {
103-
const selectedLang = useTypedSelector(store => store.playground.languageConfig.mainLanguage);
104-
105-
const currentLang = useTypedSelector(store => store.playground.conductorLanguage);
106-
const currentEval = useTypedSelector(store => store.playground.conductorEvaluator);
10736
const dispatch = useDispatch();
37+
const directoryEnabled = useFeature(flagLanguageDirectoryEnable);
38+
const selectedLanguageId = useTypedSelector(s => s.languageDirectory.selectedLanguageId);
39+
const selectedEvaluatorId = useTypedSelector(s => s.languageDirectory.selectedEvaluatorId);
40+
const dirLanguages = useTypedSelector(s => s.languageDirectory.languages);
41+
42+
useEffect(() => {
43+
if (directoryEnabled && dirLanguages.length === 0) {
44+
dispatch(LanguageDirectoryActions.fetchLanguages());
45+
}
46+
}, [directoryEnabled, dirLanguages.length, dispatch]);
10847

109-
const conductorEnabled = useFeature(flagConductorEnable);
110-
if (conductorEnabled) {
111-
const handleChapterSelect = (evaluator: IEvaluatorDefinition) => {
112-
dispatch(playgroundConductorEvaluator(evaluator));
113-
};
48+
if (!directoryEnabled) {
11449
return (
115-
<EvaluatorSelectComponent
116-
items={currentLang.evaluators}
117-
onItemSelect={handleChapterSelect}
118-
itemRenderer={evaluatorRenderer}
119-
itemListRenderer={evaluatorListRenderer}
120-
filterable={false}
50+
<LegacyControlBarChapterSelect
51+
isFolderModeEnabled={isFolderModeEnabled}
52+
sourceChapter={sourceChapter}
53+
sourceVariant={sourceVariant}
54+
handleChapterSelect={handleChapterSelect}
12155
disabled={disabled}
122-
>
123-
<Button
124-
minimal
125-
text={currentEval?.name}
126-
rightIcon={disabled ? null : IconNames.DOUBLE_CARET_VERTICAL}
127-
disabled={disabled}
128-
/>
129-
</EvaluatorSelectComponent>
56+
/>
13057
);
13158
}
13259

133-
const choices = [
134-
...sourceLanguages,
135-
// Full JS/TS version uses eval(), which is a huge security risk, so we only enable
136-
// for public deployments. HTML, while sandboxed, is treated the same way to be safe.
137-
// See https://github.com/source-academy/frontend/pull/2460#issuecomment-1528759912
138-
...(Constants.playgroundOnly ? [fullJSLanguage, fullTSLanguage, htmlLanguage] : []),
139-
...schemeLanguages,
140-
...pyLanguages,
141-
...javaLanguages
142-
];
60+
const EvaluatorSelectComponent = Select.ofType<IEvaluatorDefinition>();
61+
62+
const currentLanguage = dirLanguages.find(l => l.id === selectedLanguageId);
63+
const evaluators = currentLanguage?.evaluators ?? [];
64+
const selectedEvaluator = evaluators.find(e => e.id === selectedEvaluatorId);
65+
66+
const evaluatorListRenderer: ItemListRenderer<IEvaluatorDefinition> = ({
67+
itemsParentRef,
68+
renderItem,
69+
items
70+
}) => (
71+
<Menu ulRef={itemsParentRef} style={{ display: 'flex', flexDirection: 'column' }}>
72+
{items.map(renderItem)}
73+
</Menu>
74+
);
75+
76+
const evaluatorRenderer: ItemRenderer<IEvaluatorDefinition> = (evaluator, { handleClick }) => (
77+
<MenuItem key={evaluator.id} onClick={handleClick} text={evaluator.name} />
78+
);
79+
80+
const onSelectEvaluator = (evaluator: IEvaluatorDefinition) => {
81+
dispatch(LanguageDirectoryActions.setSelectedEvaluator(evaluator.id));
82+
};
14383

14484
return (
145-
<ChapterSelectComponent
146-
items={choices.filter(({ mainLanguage }) => mainLanguage === selectedLang)}
147-
onItemSelect={handleChapterSelect}
148-
itemRenderer={chapterRenderer(isFolderModeEnabled)}
149-
itemListRenderer={chapterListRenderer}
85+
<EvaluatorSelectComponent
86+
items={evaluators}
87+
onItemSelect={onSelectEvaluator}
88+
itemRenderer={evaluatorRenderer}
89+
itemListRenderer={evaluatorListRenderer}
15090
filterable={false}
15191
disabled={disabled}
15292
>
15393
<Button
15494
minimal
155-
text={styliseSublanguage(sourceChapter, sourceVariant)}
95+
text={selectedEvaluator ? selectedEvaluator.name : 'Select Evaluator'}
15696
rightIcon={disabled ? null : IconNames.DOUBLE_CARET_VERTICAL}
97+
data-testid="ControlBarEvaluatorSelect"
15798
disabled={disabled}
15899
/>
159-
</ChapterSelectComponent>
100+
</EvaluatorSelectComponent>
160101
);
161102
};
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
import { Button, Menu, MenuItem, Tooltip } from '@blueprintjs/core';
2+
import { IconNames } from '@blueprintjs/icons';
3+
import { ItemListRenderer, ItemRenderer, Select } from '@blueprintjs/select';
4+
import { Chapter, Variant } from 'js-slang/dist/types';
5+
import React from 'react';
6+
7+
import {
8+
fullJSLanguage,
9+
fullTSLanguage,
10+
htmlLanguage,
11+
javaLanguages,
12+
pyLanguages,
13+
SALanguage,
14+
schemeLanguages,
15+
sourceLanguages,
16+
styliseSublanguage
17+
} from '../application/ApplicationTypes';
18+
import Constants from '../utils/Constants';
19+
import { useTypedSelector } from '../utils/Hooks';
20+
21+
type ControlBarChapterSelectProps = DispatchProps & StateProps;
22+
23+
type DispatchProps = {
24+
handleChapterSelect?: (i: SALanguage, e?: React.SyntheticEvent<HTMLElement>) => void;
25+
};
26+
27+
type StateProps = {
28+
isFolderModeEnabled: boolean;
29+
sourceChapter: Chapter;
30+
sourceVariant: Variant;
31+
disabled?: boolean;
32+
};
33+
34+
const chapterListRenderer: ItemListRenderer<SALanguage> = ({
35+
itemsParentRef,
36+
renderItem,
37+
items
38+
}) => {
39+
const defaultChoices = items.filter(({ variant }) => variant === Variant.DEFAULT);
40+
const variantChoices = items.filter(({ variant }) => variant !== Variant.DEFAULT);
41+
42+
return (
43+
<Menu ulRef={itemsParentRef} style={{ display: 'flex', flexDirection: 'column' }}>
44+
{defaultChoices.map(renderItem)}
45+
{variantChoices.length > 0 && (
46+
<MenuItem key="variant-menu" text="Variants" icon="cog">
47+
{variantChoices.map(renderItem)}
48+
</MenuItem>
49+
)}
50+
</Menu>
51+
);
52+
};
53+
54+
const chapterRenderer: (isFolderModeEnabled: boolean) => ItemRenderer<SALanguage> =
55+
(isFolderModeEnabled: boolean) =>
56+
(lang, { handleClick }) => {
57+
const isDisabled = isFolderModeEnabled && lang.chapter === Chapter.SOURCE_1;
58+
const tooltipContent = isDisabled
59+
? 'Folder mode makes use of lists which are not available in Source 1. To switch to Source 1, disable Folder mode.'
60+
: undefined;
61+
return (
62+
<Tooltip
63+
key={lang.displayName}
64+
content={tooltipContent}
65+
disabled={tooltipContent === undefined}
66+
>
67+
<MenuItem onClick={handleClick} text={lang.displayName} disabled={isDisabled} />
68+
</Tooltip>
69+
);
70+
};
71+
72+
const ChapterSelectComponent = Select.ofType<SALanguage>();
73+
74+
export const LegacyControlBarChapterSelect: React.FC<ControlBarChapterSelectProps> = ({
75+
isFolderModeEnabled,
76+
sourceChapter,
77+
sourceVariant,
78+
handleChapterSelect = () => {},
79+
disabled = false
80+
}) => {
81+
const selectedLang = useTypedSelector(store => store.playground.languageConfig.mainLanguage);
82+
83+
const choices = [
84+
...sourceLanguages,
85+
// Full JS/TS version uses eval(), which is a huge security risk, so we only enable
86+
// for public deployments. HTML, while sandboxed, is treated the same way to be safe.
87+
// See https://github.com/source-academy/frontend/pull/2460#issuecomment-1528759912
88+
...(Constants.playgroundOnly ? [fullJSLanguage, fullTSLanguage, htmlLanguage] : []),
89+
...schemeLanguages,
90+
...pyLanguages,
91+
...javaLanguages
92+
];
93+
94+
return (
95+
<ChapterSelectComponent
96+
items={choices.filter(({ mainLanguage }) => mainLanguage === selectedLang)}
97+
onItemSelect={handleChapterSelect}
98+
itemRenderer={chapterRenderer(isFolderModeEnabled)}
99+
itemListRenderer={chapterListRenderer}
100+
filterable={false}
101+
disabled={disabled}
102+
>
103+
<Button
104+
minimal
105+
text={styliseSublanguage(sourceChapter, sourceVariant)}
106+
rightIcon={disabled ? null : IconNames.DOUBLE_CARET_VERTICAL}
107+
disabled={disabled}
108+
/>
109+
</ChapterSelectComponent>
110+
);
111+
};
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
import { flagConductorEnable } from '../../features/conductor/flagConductorEnable';
22
import { flagConductorEvaluatorUrl } from '../../features/conductor/flagConductorEvaluatorUrl';
3-
import { flagLangDirUrl } from '../directory/flagLangDirUrl';
3+
import { flagLanguageDirectoryEnable } from '../../features/languageDirectory/flagLanguageDirectory';
44
import { FeatureFlag } from './FeatureFlag';
55

66
export const publicFlags: FeatureFlag<any>[] = [
77
flagConductorEnable,
88
flagConductorEvaluatorUrl,
9-
flagLangDirUrl
9+
flagLanguageDirectoryEnable
1010
];

0 commit comments

Comments
 (0)