Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
ff284a3
integrate conductor into frontend
tsammeow Feb 3, 2025
4667807
upgrade yarn(4.6.0)
tsammeow Feb 4, 2025
0ce4f9c
Merge branch 'master'
tsammeow Feb 4, 2025
bef90bb
fix lint issues
tsammeow Feb 4, 2025
c9165a4
Merge branch 'master' into master
RichDom2185 Feb 5, 2025
b085079
fix gl resolution problem
tsammeow Feb 5, 2025
711fd73
change github actions to use --immutable instead of --frozen-lockfile…
tsammeow Feb 5, 2025
309eb9e
add sa-conductor to list of ignored module paths in jest config
tsammeow Feb 5, 2025
2e3a25a
readd gl@^8.0.2 resolution to package.json
tsammeow Feb 5, 2025
b444a49
format with prettier
tsammeow Feb 5, 2025
2a10429
update caniuse-lite
tsammeow Feb 5, 2025
614ea2a
update sa-conductor(0.0.13)
tsammeow Feb 8, 2025
b575d9d
add ui to edit feature flags
tsammeow Feb 10, 2025
e788347
support repl, and use stop button to kill runner instead of timeout
tsammeow Feb 10, 2025
2c1a17f
extract actual feature selector to separate function and rename selec…
tsammeow Feb 10, 2025
4268f4d
stop replReducer setting isRunning to true when using conductor, as i…
tsammeow Feb 10, 2025
ce8c600
revert replReducer change as it causes an error when using the repl
tsammeow Feb 11, 2025
6b16a6f
hide eval button if not running when using conductor
tsammeow Feb 11, 2025
2a07968
Merge branch 'master' of https://github.com/source-academy/frontend i…
RichDom2185 Feb 15, 2025
1426f25
update lockfile after package.json change
tsammeow Feb 16, 2025
47f7864
upgrade sa-conductor(0.1.3) to use new file communication protocol
tsammeow Feb 16, 2025
5fc4dc6
upgrade sa-conductor(0.1.4)
tsammeow Feb 16, 2025
76d0f6c
Merge branch 'master' into master
martin-henz Feb 18, 2025
72ae405
update location of sa-languages repository
tsammeow Feb 18, 2025
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
2 changes: 1 addition & 1 deletion .github/workflows/build-development.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ jobs:
run: echo "time=$(date -Iseconds)" >> $GITHUB_OUTPUT
- name: yarn install and build
run: |
yarn install --frozen-lockfile
yarn install --immutable
yarn run build
env:
REACT_APP_URL_SHORTENER_SIGNATURE: ${{ secrets.REACT_APP_URL_SHORTENER_SIGNATURE }}
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ jobs:
node-version: 20
cache: yarn
- name: Install dependencies
run: yarn install --frozen-lockfile
run: yarn install --immutable
- name: Run command
run: yarn run ${{ matrix.commands }}
- name: Coveralls - Upload test coverage report
Expand Down
16 changes: 16 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -34,3 +34,19 @@ yarn-error.log
# emacs backup files

*~

# yarn files

.yarn/*
!.yarn/patches
!.yarn/plugins
!.yarn/releases
!.yarn/sdks
!.yarn/versions

# Swap the comments on the following lines if you wish to use zero-installs
# In that case, don't forget to run `yarn config set enableGlobalCache false`!
# Documentation here: https://yarnpkg.com/features/caching#zero-installs

#!.yarn/cache
.pnp.*
1 change: 1 addition & 0 deletions .yarnrc.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
nodeLinker: node-modules
3 changes: 2 additions & 1 deletion craco.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,8 @@ const cracoConfig = {
'split-on-first',
'filter-obj',
'@sourceacademy/c-slang',
'java-parser'
'java-parser',
'sa-conductor'
),
'^.+\\.module\\.(css|sass|scss)$'
];
Expand Down
6 changes: 4 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
{
"private": true,
"name": "frontend",
"packageManager": "[email protected]",
"version": "1.4.3",
"scripts-info": {
"analyze": "Analyze bundle size breakdown",
Expand Down Expand Up @@ -91,6 +92,8 @@
"redux-mock-store": "^1.5.4",
"redux-saga": "^1.2.3",
"rehype-react": "^8.0.0",
"sa-conductor": "https://github.com/tsammeow/sa-conductor.git#0.1.4",
"sa-languages": "https://github.com/source-academy/sa-languages.git",
"showdown": "^2.1.0",
"sourceror": "^0.8.5",
"unified": "^11.0.0",
Expand Down Expand Up @@ -138,7 +141,6 @@
"cross-env": "^7.0.3",
"eslint": "^9.9.0",
"eslint-plugin-react": "^7.35.0",
"//": "See: https://github.com/facebook/react/issues/28313#issuecomment-2076798972, https://github.com/t3-oss/create-t3-turbo/issues/984#issuecomment-2076413457",
"eslint-plugin-react-hooks": "5.1.0-canary-cb151849e1-20240424",
"eslint-plugin-react-refresh": "^0.4.9",
"eslint-plugin-simple-import-sort": "^12.1.1",
Expand All @@ -164,7 +166,7 @@
"webpack-bundle-analyzer": "^4.9.0"
},
"resolutions": {
"**/gl": "^8.0.2"
"gl": "^8.0.2"
},
"browserslist": {
"production": [
Expand Down
3 changes: 3 additions & 0 deletions src/commons/application/ApplicationTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { PlaybackStatus, RecordingStatus } from '../../features/sourceRecorder/S
import { StoriesEnvState, StoriesState } from '../../features/stories/StoriesTypes';
import { freshSortState } from '../../pages/academy/grading/subcomponents/GradingSubmissionsTable';
import { WORKSPACE_BASE_PATHS } from '../../pages/fileSystem/createInBrowserFileSystem';
import { defaultFeatureFlags, FeatureFlagsState } from '../featureFlags';
import { FileSystemState } from '../fileSystem/FileSystemTypes';
import { SideContentManagerState, SideContentState } from '../sideContent/SideContentTypes';
import Constants from '../utils/Constants';
Expand All @@ -29,6 +30,7 @@ export type OverallState = {
readonly stories: StoriesState;
readonly workspaces: WorkspaceManagerState;
readonly dashboard: DashboardState;
readonly featureFlags: FeatureFlagsState;
readonly fileSystem: FileSystemState;
readonly sideContent: SideContentManagerState;
};
Expand Down Expand Up @@ -612,6 +614,7 @@ export const defaultState: OverallState = {
session: defaultSession,
stories: defaultStories,
workspaces: defaultWorkspaceManager,
featureFlags: defaultFeatureFlags,
fileSystem: defaultFileSystem,
sideContent: defaultSideContentManager
};
2 changes: 2 additions & 0 deletions src/commons/application/reducers/RootReducer.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { combineReducers, Reducer } from '@reduxjs/toolkit';
import { FeatureFlagsReducer as featureFlags } from 'src/commons/featureFlags';
import { SourceActionType } from 'src/commons/utils/ActionsHelper';

import { AchievementReducer as achievement } from '../../../features/achievement/AchievementReducer';
Expand All @@ -20,6 +21,7 @@ const rootReducer: Reducer<OverallState, SourceActionType> = combineReducers({
session,
stories,
workspaces,
featureFlags,
fileSystem,
sideContent
});
Expand Down
14 changes: 12 additions & 2 deletions src/commons/controlBar/ControlBarAutorunButtons.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import { Switch } from '@blueprintjs/core';
import { IconNames } from '@blueprintjs/icons';
import React from 'react';
import { flagConductorEnable } from 'src/features/conductor/flagConductorEnable';

import ControlButton from '../ControlButton';
import { useFeature } from '../featureFlags/useFeature';
import { useResponsive } from '../utils/Hooks';
import { ControlBarRunButton } from './ControlBarRunButton';

Expand Down Expand Up @@ -46,7 +48,8 @@ export const ControlBarAutorunButtons: React.FC<ControlBarAutorunButtonProps> =

// stop button does not do anything due to the blocking nature of eval methods (e.g. runInContext)
// to prevent "flickering", we will just disable Stop Button for now
const showStopButton = false && (
const conductorEnabled = useFeature(flagConductorEnable);
const showStopButton = conductorEnabled && props.isRunning && (
<ControlButton label="Stop" icon={IconNames.STOP} onClick={props.handleInterruptEval} />
);

Expand Down Expand Up @@ -87,7 +90,14 @@ export const ControlBarAutorunButtons: React.FC<ControlBarAutorunButtonProps> =
/>
</div>
)}
{showAutoRunIndicator || showStopButton || showRunButton}
{conductorEnabled ? (
<>
{showAutoRunIndicator || showRunButton}
{showStopButton}
</>
) : (
showAutoRunIndicator || showStopButton || showRunButton
)}
{showDebuggerPause}
{showDebuggerResume}
{showDebuggerReset('Stop Debugger')}
Expand Down
8 changes: 6 additions & 2 deletions src/commons/controlBar/ControlBarEvalButton.tsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,22 @@
import { Tooltip } from '@blueprintjs/core';
import { IconNames } from '@blueprintjs/icons';
import React from 'react';
import { flagConductorEnable } from 'src/features/conductor/flagConductorEnable';

import ControlButton from '../ControlButton';
import { useFeature } from '../featureFlags/useFeature';

type Props = {
handleReplEval: () => void;
isRunning: boolean;
};

export const ControlBarEvalButton: React.FC<Props> = ({ handleReplEval, isRunning }) => {
return isRunning ? null : (
const conductorEnabled = useFeature(flagConductorEnable);
const showEvalButton = conductorEnabled ? isRunning : !isRunning;
return showEvalButton ? (
<Tooltip content="...or press shift-enter in the REPL">
<ControlButton label="Eval" icon={IconNames.CODE} onClick={handleReplEval} />
</Tooltip>
);
) : null;
};
13 changes: 13 additions & 0 deletions src/commons/dropdown/DropdownSettings.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import {
Button,
Dialog,
DialogBody,
FormGroup,
Expand All @@ -9,6 +10,7 @@ import {
} from '@blueprintjs/core';
import { IconNames } from '@blueprintjs/icons';
import React, { useContext } from 'react';
import { useNavigate } from 'react-router-dom';

import { EditorBinding, WorkspaceSettingsContext } from '../WorkspaceSettingsContext';
import LocaleSelector from './LocaleSelector';
Expand All @@ -35,6 +37,8 @@ const DropdownSettings: React.FC<Props> = ({ isOpen, onClose }) => {
});
};

const navigate = useNavigate();

return (
<Dialog
className="settings"
Expand All @@ -61,6 +65,15 @@ const DropdownSettings: React.FC<Props> = ({ isOpen, onClose }) => {
</Tooltip>
</FormGroup>
<LocaleSelector />
<Button
onClick={() => {
navigate('/features');
onClose();
}}
icon={IconNames.FLAG}
>
Feature Flags
</Button>
</DialogBody>
</Dialog>
);
Expand Down
21 changes: 21 additions & 0 deletions src/commons/featureFlags/FeatureFlag.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
export class FeatureFlag<T> {
private readonly _flagName: string;
private readonly _defaultValue: T;
private readonly _flagDesc?: string;

get flagName(): string {
return this._flagName;
}
get defaultValue(): T {
return this._defaultValue;
}
get flagDesc(): string | undefined {
return this._flagDesc;
}

constructor(flagName: string, defaultValue: T, flagDesc?: string) {
this._flagName = flagName;
this._defaultValue = defaultValue;
this._flagDesc = flagDesc;
}
}
5 changes: 5 additions & 0 deletions src/commons/featureFlags/featureSelector.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { OverallState } from '../application/ApplicationTypes';
import { FeatureFlag } from './FeatureFlag';

export const featureSelector = (featureFlag: FeatureFlag<any>) => (state: OverallState) =>
state.featureFlags.modifiedFlags[featureFlag.flagName];
39 changes: 39 additions & 0 deletions src/commons/featureFlags/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { createSlice } from '@reduxjs/toolkit';

import { FeatureFlag } from './FeatureFlag';

export type FeatureFlagsState = {
modifiedFlags: Record<string, any>;
};

export const defaultFeatureFlags = {
modifiedFlags: {}
};

const featureFlagsSlice = createSlice({
name: 'featureFlags',
initialState: defaultFeatureFlags,
reducers: {
setFlag<T>(
state: FeatureFlagsState,
action: { payload: { featureFlag: FeatureFlag<T>; value: T } }
) {
state.modifiedFlags[action.payload.featureFlag.flagName] = action.payload.value;
},
resetFlag<T>(state: FeatureFlagsState, action: { payload: { featureFlag: FeatureFlag<T> } }) {
delete state.modifiedFlags[action.payload.featureFlag.flagName];
}
}
});

export const FeatureFlagsActions = featureFlagsSlice.actions;

export const FeatureFlagsReducer = featureFlagsSlice.reducer;

export function createFeatureFlag<T>(
flagName: string,
defaultValue: T,
flagDesc?: string
): FeatureFlag<T> {
return new FeatureFlag<T>(flagName, defaultValue, flagDesc);
}
6 changes: 6 additions & 0 deletions src/commons/featureFlags/publicFlags.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { flagConductorEnable } from 'src/features/conductor/flagConductorEnable';
import { flagConductorEvaluatorUrl } from 'src/features/conductor/flagConductorEvaluatorUrl';

import { FeatureFlag } from './FeatureFlag';

export const publicFlags: FeatureFlag<any>[] = [flagConductorEnable, flagConductorEvaluatorUrl];
11 changes: 11 additions & 0 deletions src/commons/featureFlags/selectFeatureSaga.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { SagaIterator } from 'redux-saga';
import { select } from 'redux-saga/effects';

import { FeatureFlag } from './FeatureFlag';
import { featureSelector } from './featureSelector';

export function* selectFeatureSaga<T>(featureFlag: FeatureFlag<T>): SagaIterator<T> {
const { defaultValue } = featureFlag;
const flagValue = yield select(featureSelector(featureFlag));
return flagValue ?? defaultValue;
}
8 changes: 8 additions & 0 deletions src/commons/featureFlags/useFeature.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { useTypedSelector } from '../utils/Hooks';
import { FeatureFlag } from './FeatureFlag';

export function useFeature<T>(featureFlag: FeatureFlag<T>): T {
const { flagName, defaultValue } = featureFlag;
const flagValue = useTypedSelector(state => state.featureFlags.modifiedFlags[flagName]);
return flagValue ?? defaultValue;
}
2 changes: 2 additions & 0 deletions src/commons/mocks/StoreMocks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
defaultWorkspaceManager,
OverallState
} from '../application/ApplicationTypes';
import { defaultFeatureFlags } from '../featureFlags';
import { SourceActionType } from '../utils/ActionsHelper';
import { DeepPartial } from '../utils/TypeHelper';

Expand All @@ -29,6 +30,7 @@ export function mockInitialStore(
workspaces: defaultWorkspaceManager,
session: defaultSession,
stories: defaultStories,
featureFlags: defaultFeatureFlags,
fileSystem: defaultFileSystem,
sideContent: defaultSideContentManager
};
Expand Down
Loading
Loading