diff --git a/package.json b/package.json index a7a873dbf9..bc5c0e5a04 100644 --- a/package.json +++ b/package.json @@ -46,11 +46,13 @@ "@sentry/react": "^10.5.0", "@sourceacademy/autocomplete": "github:source-academy/autocomplete#e669d9ed98753350a3c8433a92985227eb789663", "@sourceacademy/c-slang": "^1.0.21", + "@sourceacademy/common-tabs": "file:../plugins/src/common/tabs", "@sourceacademy/conductor": "https://github.com/source-academy/conductor.git#0.4.0", "@sourceacademy/language-directory": "https://github.com/source-academy/language-directory.git#0.0.6", "@sourceacademy/plugin-directory": "https://github.com/source-academy/plugin-directory.git#0.0.2", "@sourceacademy/sharedb-ace": "2.1.1", "@sourceacademy/sling-client": "^0.1.0", + "@sourceacademy/web-test": "file:..\\plugins\\src\\web\\test", "@szhsin/react-menu": "^4.0.0", "@tanstack/react-query": "^5.100.14", "@tanstack/react-table": "^8.9.3", diff --git a/src/commons/assessmentWorkspace/AssessmentWorkspace.tsx b/src/commons/assessmentWorkspace/AssessmentWorkspace.tsx index 31c617cb5f..3fab06d69b 100644 --- a/src/commons/assessmentWorkspace/AssessmentWorkspace.tsx +++ b/src/commons/assessmentWorkspace/AssessmentWorkspace.tsx @@ -72,7 +72,11 @@ import SideContentToneMatrix from '../sideContent/content/SideContentToneMatrix' import type { SideContentProps } from '../sideContent/SideContent'; import { changeSideContentHeight } from '../sideContent/SideContentActions'; import { useSideContent } from '../sideContent/SideContentHelper'; -import { type SideContentTab, SideContentType } from '../sideContent/SideContentTypes'; +import { + type SideContentTab, + type SideContentTabId, + SideContentType, +} from '../sideContent/SideContentTypes'; import Constants from '../utils/Constants'; import { useResponsive, useTypedSelector } from '../utils/Hooks'; import { assessmentTypeLink } from '../utils/ParamParseHelper'; @@ -666,8 +670,8 @@ function AssessmentWorkspace(props: AssessmentWorkspaceProps) { } const onChangeTabs = ( - newTabId: SideContentType, - prevTabId: SideContentType, + newTabId: SideContentTabId, + prevTabId: SideContentTabId, event: React.MouseEvent, ) => { if (newTabId === prevTabId) { @@ -879,8 +883,8 @@ function AssessmentWorkspace(props: AssessmentWorkspaceProps) { const mobileSideContentProps: (q: number) => MobileSideContentProps = (questionId: number) => { const onChangeTabs = ( - newTabId: SideContentType, - prevTabId: SideContentType, + newTabId: SideContentTabId, + prevTabId: SideContentTabId, event: React.MouseEvent, ) => { if (newTabId === prevTabId) { diff --git a/src/commons/mobileWorkspace/MobileWorkspace.tsx b/src/commons/mobileWorkspace/MobileWorkspace.tsx index fda1473735..e3124d8c3d 100644 --- a/src/commons/mobileWorkspace/MobileWorkspace.tsx +++ b/src/commons/mobileWorkspace/MobileWorkspace.tsx @@ -11,7 +11,11 @@ import McqChooser, { type McqChooserProps } from '../mcqChooser/McqChooser'; import { Prompt } from '../ReactRouterPrompt'; import type { ReplProps } from '../repl/Repl'; import type { SideBarTab } from '../sideBar/SideBar'; -import { type SideContentTab, SideContentType } from '../sideContent/SideContentTypes'; +import { + type SideContentTab, + type SideContentTabId, + SideContentType, +} from '../sideContent/SideContentTypes'; import DraggableRepl from './DraggableRepl'; import MobileKeyboard from './MobileKeyboard'; import MobileSideContent, { @@ -169,7 +173,7 @@ function MobileWorkspace(props: MobileWorkspaceProps) { const handleEditorEval = props.editorContainerProps?.handleEditorEval; const handleTabChangeForRepl = useCallback( - (newTabId: SideContentType, prevTabId: SideContentType) => { + (newTabId: SideContentTabId, prevTabId: SideContentTabId) => { // Evaluate program upon pressing the run tab. if (newTabId === SideContentType.mobileEditorRun) { handleEditorEval?.(); @@ -210,8 +214,8 @@ function MobileWorkspace(props: MobileWorkspaceProps) { const onChange = props.mobileSideContentProps.onChange; const onSideContentTabChange = useCallback( ( - newTabId: SideContentType, - prevTabId: SideContentType, + newTabId: SideContentTabId, + prevTabId: SideContentTabId, event: React.MouseEvent, ) => { onChange(newTabId, prevTabId, event); diff --git a/src/commons/mobileWorkspace/mobileSideContent/MobileSideContent.tsx b/src/commons/mobileWorkspace/mobileSideContent/MobileSideContent.tsx index edf05c6116..0a99967472 100644 --- a/src/commons/mobileWorkspace/mobileSideContent/MobileSideContent.tsx +++ b/src/commons/mobileWorkspace/mobileSideContent/MobileSideContent.tsx @@ -10,7 +10,7 @@ import { type ChangeTabsCallback, type SideContentLocation, type SideContentTab, - SideContentType, + type SideContentTabId, } from '../../sideContent/SideContentTypes'; import { propsAreEqual } from '../../utils/MemoizeHelper'; import MobileControlBar from './MobileControlBar'; @@ -66,7 +66,7 @@ function MobileSideContent({ * renderedPanels is not memoized since a change in selectedTabId (when changing tabs) * would force React.useMemo to recompute the nullary function anyway */ - const renderedPanels = (dynamicTabs: SideContentTab[], selectedTabId?: SideContentType) => { + const renderedPanels = (dynamicTabs: SideContentTab[], selectedTabId?: SideContentTabId) => { // TODO: Fix the CSS of all the panels (e.g. subst_visualizer) const renderPanel = (tab: SideContentTab, workspaceLocation?: SideContentLocation) => { if (!tab.body) return; diff --git a/src/commons/sagas/AchievementSaga.ts b/src/commons/sagas/AchievementSaga.ts index 8160900ace..01b9221de5 100644 --- a/src/commons/sagas/AchievementSaga.ts +++ b/src/commons/sagas/AchievementSaga.ts @@ -10,7 +10,7 @@ import type { Tokens } from '../application/types/SessionTypes'; import { combineSagaHandlers } from '../redux/utils'; import SideContentActions from '../sideContent/SideContentActions'; import { getLocation } from '../sideContent/SideContentHelper'; -import { SideContentType } from '../sideContent/SideContentTypes'; +import { type SideContentTabId, SideContentType } from '../sideContent/SideContentTypes'; import { actions } from '../utils/ActionsHelper'; import Constants from '../utils/Constants'; import { selectTokens } from './BackendSaga'; @@ -145,7 +145,7 @@ const AchievementSaga = combineSagaHandlers({ (state: OverallState) => state.session.enableAchievements, ); if (workspaceLocation !== undefined && eventNames.find(e => e === EventType.ERROR)) { - const selectedTab: SideContentType | undefined = yield select((state: OverallState) => { + const selectedTab: SideContentTabId | undefined = yield select((state: OverallState) => { const [loc] = getLocation(workspaceLocation); return state.sideContent[loc].selectedTab; }); diff --git a/src/commons/sagas/SideContentSaga.ts b/src/commons/sagas/SideContentSaga.ts index ea252067cd..393dfc9f76 100644 --- a/src/commons/sagas/SideContentSaga.ts +++ b/src/commons/sagas/SideContentSaga.ts @@ -7,6 +7,7 @@ import { getLocation } from '../sideContent/SideContentHelper'; import { type SideContentLocation, type SideContentManagerState, + type SideContentTabId, SideContentType, } from '../sideContent/SideContentTypes'; import WorkspaceActions from '../workspace/WorkspaceActions'; @@ -25,7 +26,7 @@ const isVisitSideContent = ( const selectSelectedTab = ( state: any, workspaceLocation: SideContentLocation, -): SideContentType | undefined => { +): SideContentTabId | undefined => { const sideContentState = (state.sideContent ?? state) as SideContentManagerState; const [location] = getLocation(workspaceLocation); return sideContentState[location]?.selectedTab; @@ -38,7 +39,7 @@ const SideContentSaga = combineSagaHandlers({ // When a program finishes evaluation, we clear all alerts, // So we must wait until after and all module tabs have been spawned // to process any kind of alerts that were raised by non-module side content - const selectedTab: SideContentType | undefined = yield select((state: any) => + const selectedTab: SideContentTabId | undefined = yield select((state: any) => selectSelectedTab(state, workspaceLocation), ); @@ -72,7 +73,7 @@ const SideContentSaga = combineSagaHandlers({ return; } - const selectedTabAfterWait: SideContentType | undefined = yield select((state: any) => + const selectedTabAfterWait: SideContentTabId | undefined = yield select((state: any) => selectSelectedTab(state, workspaceLocation), ); diff --git a/src/commons/sagas/WorkspaceSaga/helpers/evalCode.ts b/src/commons/sagas/WorkspaceSaga/helpers/evalCode.ts index 2c62ac78ae..86d67d37a2 100644 --- a/src/commons/sagas/WorkspaceSaga/helpers/evalCode.ts +++ b/src/commons/sagas/WorkspaceSaga/helpers/evalCode.ts @@ -22,6 +22,7 @@ import { selectConductorEnable } from '../../../../features/conductor/flagConduc import LanguageDirectoryActions from '../../../../features/directory/LanguageDirectoryActions'; import { type OverallState } from '../../../application/ApplicationTypes'; import { visitSideContent } from '../../../sideContent/SideContentActions'; +import sideContentManager from '../../../sideContent/SideContentManager'; import { SideContentType } from '../../../sideContent/SideContentTypes'; import { actions } from '../../../utils/ActionsHelper'; import DisplayBufferService from '../../../utils/DisplayBufferService'; @@ -548,7 +549,7 @@ export function* evalCodeConductorSaga( // Reuse a preloaded conductor instance when available. const { hostPlugin, conduit }: { hostPlugin: BrowserHostPlugin; conduit: IConduit } = yield call( getPreparedConductorSaga, - { files, consume: true }, + { files, consume: true, workspaceLocation }, ); // Begin evaluation @@ -574,6 +575,7 @@ export function* evalCodeConductorSaga( } yield cancel(statusTask); yield call([conduit, 'terminate']); + yield call([sideContentManager, sideContentManager.clearTabs]); yield cancel(stdoutTask); yield cancel(resultTask); yield cancel(errorTask); diff --git a/src/commons/sagas/helpers/conductorEvaluatorCache.ts b/src/commons/sagas/helpers/conductorEvaluatorCache.ts index 0436741c48..54bad7a1a4 100644 --- a/src/commons/sagas/helpers/conductorEvaluatorCache.ts +++ b/src/commons/sagas/helpers/conductorEvaluatorCache.ts @@ -1,9 +1,16 @@ import type { IConduit } from '@sourceacademy/conductor/conduit'; +import { PluginType } from '@sourceacademy/plugin-directory'; import type { SagaIterator } from 'redux-saga'; -import { call } from 'redux-saga/effects'; +import { call, select } from 'redux-saga/effects'; +import { requireProvider } from 'src/commons/sideContent/SideContentHelper'; +import { registry } from 'src/features/conductor/Registry'; +import { selectDirectoryPluginUrl } from 'src/features/directory/flagDirectoryPluginUrl'; import type { BrowserHostPlugin } from '../../../features/conductor/BrowserHostPlugin'; import { createConductor } from '../../../features/conductor/createConductor'; +import type { OverallState } from '../../application/ApplicationTypes'; +import sideContentManager from '../../sideContent/SideContentManager'; +import type { SideContentLocation } from '../../sideContent/SideContentTypes'; type PreparedConductor = { path: string; @@ -16,6 +23,7 @@ type PreparedConductor = { type GetPreparedConductorOptions = { files?: Record; consume?: boolean; + workspaceLocation?: SideContentLocation; }; let preparedConductorPath: string | null = null; @@ -23,6 +31,17 @@ let preparedConductor: PreparedConductor | null = null; let loadingConductorPath: string | null = null; let loadingConductorPromise: Promise | null = null; let currentEvaluatorPath: string | null = null; +let currentPluginDirectoryUrl: string | null = null; +let currentPluginMap: OverallState['pluginDirectory']['pluginMap'] = {}; + +function getWebPluginLocation(pluginName: string): string | undefined { + return currentPluginMap[pluginName]?.resolutions[PluginType.WEB]; +} + +function* updatePluginDirectorySnapshotSaga(): SagaIterator { + currentPluginMap = yield select((state: OverallState) => state.pluginDirectory.pluginMap); + currentPluginDirectoryUrl = yield select(selectDirectoryPluginUrl); +} async function fetchEvaluatorObjectUrl(path: string): Promise { const evaluatorResponse = await fetch(path); @@ -40,6 +59,7 @@ async function terminatePreparedConductor(conductor: PreparedConductor | null) { } await conductor.conduit.terminate(); + sideContentManager.clearTabs(); URL.revokeObjectURL(conductor.evaluatorUrl); } @@ -61,8 +81,28 @@ async function createPreparedConductor(path: string): Promise const { hostPlugin, conduit } = createConductor( evaluatorUrl, async (fileName: string) => currentFiles[fileName], - (_pluginName: string) => { - // TODO: implement dynamic plugin loading + async (pluginName: string) => { + if (registry.has(pluginName)) { + const pluginClass = registry.get(pluginName)!; + conduit.registerPlugin(pluginClass, sideContentManager); + return; + } + + let pluginClassLocation = getWebPluginLocation(pluginName); + if (!pluginClassLocation) { + console.warn(`No web plugin resolution found for "${pluginName}" in the plugin directory.`); + return; + } + if (!pluginClassLocation.startsWith('http')) { + pluginClassLocation = new URL( + pluginClassLocation, + currentPluginDirectoryUrl || document.baseURI, + ).toString(); + } + await import(/* webpackIgnore: true */ pluginClassLocation) + .then(tab => tab.default(requireProvider)) + .then(plugin => conduit.registerPlugin(plugin, sideContentManager)) + .catch(error => console.error(`Unable to load external plugin "${pluginName}".`, error)); }, ); @@ -109,6 +149,7 @@ export function* preloadConductorEvaluatorSaga(path?: string): SagaIterator { return; } + yield call(updatePluginDirectorySnapshotSaga); currentEvaluatorPath = path; yield call(ensurePreparedConductorSaga, path); } @@ -125,6 +166,10 @@ export function* getPreparedConductorSaga( } const path = currentEvaluatorPath; + yield call(updatePluginDirectorySnapshotSaga); + if (options?.workspaceLocation) { + sideContentManager.setWorkspaceLocation(options.workspaceLocation); + } const prepared: PreparedConductor = yield call(ensurePreparedConductorSaga, path); const files = options?.files; const consume = options?.consume ?? false; diff --git a/src/commons/sideContent/SideContent.tsx b/src/commons/sideContent/SideContent.tsx index f759051823..8d411e3353 100644 --- a/src/commons/sideContent/SideContent.tsx +++ b/src/commons/sideContent/SideContent.tsx @@ -8,7 +8,7 @@ import type { ChangeTabsCallback, SideContentLocation, SideContentTab, - SideContentType, + SideContentTabId, } from './SideContentTypes'; export type SideContentProps = { @@ -19,8 +19,8 @@ export type SideContentProps = { afterDynamicTabs: SideContentTab[]; }; onChange?: ChangeTabsCallback; - selectedTabId?: SideContentType; - defaultTab?: SideContentType; + selectedTabId?: SideContentTabId; + defaultTab?: SideContentTabId; workspaceLocation: SideContentLocation; }; diff --git a/src/commons/sideContent/SideContentActions.ts b/src/commons/sideContent/SideContentActions.ts index f420830e36..1cf1abbece 100644 --- a/src/commons/sideContent/SideContentActions.ts +++ b/src/commons/sideContent/SideContentActions.ts @@ -1,22 +1,22 @@ import { createActions } from '../redux/utils'; import type { DebuggerContext, WorkspaceLocation } from '../workspace/WorkspaceTypes'; -import type { SideContentLocation, SideContentType } from './SideContentTypes'; +import type { SideContentLocation, SideContentTabId } from './SideContentTypes'; const SideContentActions = createActions('sideContent', { - beginAlertSideContent: (id: SideContentType, workspaceLocation: SideContentLocation) => ({ + beginAlertSideContent: (id: SideContentTabId, workspaceLocation: SideContentLocation) => ({ id, workspaceLocation, }), - endAlertSideContent: (id: SideContentType, workspaceLocation: SideContentLocation) => ({ + endAlertSideContent: (id: SideContentTabId, workspaceLocation: SideContentLocation) => ({ id, workspaceLocation, }), visitSideContent: ( - newId: SideContentType, - prevId: SideContentType | undefined, + newId: SideContentTabId, + prevId: SideContentTabId | undefined, workspaceLocation: SideContentLocation, ) => ({ newId, prevId, workspaceLocation }), - removeSideContentAlert: (id: SideContentType, workspaceLocation: SideContentLocation) => ({ + removeSideContentAlert: (id: SideContentTabId, workspaceLocation: SideContentLocation) => ({ id, workspaceLocation, }), diff --git a/src/commons/sideContent/SideContentHelper.ts b/src/commons/sideContent/SideContentHelper.ts index d99512a5b9..206699c3e9 100644 --- a/src/commons/sideContent/SideContentHelper.ts +++ b/src/commons/sideContent/SideContentHelper.ts @@ -21,10 +21,11 @@ import type { SideContentLocation, SideContentState, SideContentTab, + SideContentTabId, } from './SideContentTypes'; import { SideContentType } from './SideContentTypes'; -const requireProvider = (x: string) => { +export const requireProvider = (x: string) => { const exports = { react: React, 'react/jsx-runtime': JSXRuntime, @@ -41,7 +42,9 @@ const requireProvider = (x: string) => { return exports[x as keyof typeof exports] as any; }; -type RawTab = (provider: ReturnType) => { default: ModuleSideContent }; +export type RawTab = (provider: ReturnType) => { + default: ModuleSideContent; +}; /** * Returns an array of SideContentTabs to be spawned @@ -72,14 +75,14 @@ export const getTabId = (tab: SideContentTab) => export const generateTabAlert = (shouldAlert: boolean) => `side-content-tooltip${shouldAlert ? ' side-content-tab-alert' : ''}`; -export const useSideContent = (location: SideContentLocation, defaultTab?: SideContentType) => { +export const useSideContent = (location: SideContentLocation, defaultTab?: SideContentTabId) => { const [workspaceLocation] = getLocation(location); const { alerts, dynamicTabs, selectedTab, height }: SideContentState = useTypedSelector( state => state.sideContent[workspaceLocation], ); const dispatch = useDispatch(); const setSelectedTab = useCallback( - (newId: SideContentType) => { + (newId: SideContentTabId) => { if ( (selectedTab === SideContentType.substVisualizer || selectedTab === SideContentType.cseMachine) && diff --git a/src/commons/sideContent/SideContentManager.ts b/src/commons/sideContent/SideContentManager.ts new file mode 100644 index 0000000000..9c5ccc33f6 --- /dev/null +++ b/src/commons/sideContent/SideContentManager.ts @@ -0,0 +1,93 @@ +import type { ITabService, Tab } from '@sourceacademy/common-tabs'; + +import type { SideContentLocation, SideContentTab } from './SideContentTypes'; + +type Listener = () => void; + +type RegisteredTab = { + tab: SideContentTab; + visible: boolean; +}; + +export class TabService implements ITabService { + private readonly emptyTabs: SideContentTab[] = []; + private readonly listeners = new Set(); + private readonly tabs = new Map(); + private visibleTabs: SideContentTab[] = []; + private workspaceLocation: SideContentLocation = 'playground'; + + registerTab(tab: Tab): void { + const currentTab = this.tabs.get(tab.id); + this.tabs.set(tab.id, { + tab, + visible: currentTab?.visible ?? false, + }); + this.emit(); + } + + unregisterTab(id: string): void { + if (!this.tabs.delete(id)) { + return; + } + this.emit(); + } + + showTab(id: string): void { + this.setTabVisibility(id, true); + } + + hideTab(id: string): void { + this.setTabVisibility(id, false); + } + + clearTabs(): void { + if (this.tabs.size === 0) { + return; + } + this.tabs.clear(); + this.emit(); + } + + getTabs(workspaceLocation: SideContentLocation): SideContentTab[] { + if (workspaceLocation !== this.workspaceLocation) { + return this.emptyTabs; + } + return this.visibleTabs; + } + + setWorkspaceLocation(workspaceLocation: SideContentLocation): void { + if (this.workspaceLocation === workspaceLocation) { + return; + } + this.workspaceLocation = workspaceLocation; + this.emit(); + } + + subscribe(listener: Listener): () => void { + this.listeners.add(listener); + return () => this.listeners.delete(listener); + } + + private emit(): void { + this.visibleTabs = Array.from(this.tabs.values()) + .filter(({ visible }) => visible) + .map(({ tab }) => tab); + this.listeners.forEach(listener => listener()); + } + + private setTabVisibility(id: string, visible: boolean): void { + const currentTab = this.tabs.get(id); + if (!currentTab || currentTab.visible === visible) { + return; + } + this.tabs.set(id, { + ...currentTab, + visible, + }); + this.emit(); + } +} + +const sideContentManager = new TabService(); + +export default sideContentManager; diff --git a/src/commons/sideContent/SideContentProvider.tsx b/src/commons/sideContent/SideContentProvider.tsx index 859165b97c..2a079b8b1c 100644 --- a/src/commons/sideContent/SideContentProvider.tsx +++ b/src/commons/sideContent/SideContentProvider.tsx @@ -1,11 +1,12 @@ -import { useCallback } from 'react'; +import { useCallback, useSyncExternalStore } from 'react'; import { useSideContent } from './SideContentHelper'; +import sideContentManager from './SideContentManager'; import type { ChangeTabsCallback, SideContentLocation, SideContentTab, - SideContentType, + SideContentTabId, } from './SideContentTypes'; type SideContentProviderProps = { @@ -18,7 +19,7 @@ type SideContentProviderProps = { alerts: string[]; changeTabsCallback: ChangeTabsCallback; height?: number; - selectedTab?: SideContentType; + selectedTab?: SideContentTabId; }) => React.ReactElement; /** @@ -26,12 +27,12 @@ type SideContentProviderProps = { * then responsible for managing tab changing */ onChange?: ChangeTabsCallback; - selectedTab?: SideContentType; + selectedTab?: SideContentTabId; /** * Value to use if the currently selected tab is undefined */ - defaultTab?: SideContentType; + defaultTab?: SideContentTabId; workspaceLocation: SideContentLocation; }; @@ -56,10 +57,14 @@ export default function SideContentProvider({ workspaceLocation, defaultTab, ); + const serviceTabs = useSyncExternalStore( + sideContentManager.subscribe.bind(sideContentManager), + () => sideContentManager.getTabs(workspaceLocation), + ); const allTabs = tabs - ? [...tabs.beforeDynamicTabs, ...dynamicTabs, ...tabs.afterDynamicTabs] - : dynamicTabs; + ? [...tabs.beforeDynamicTabs, ...dynamicTabs, ...serviceTabs, ...tabs.afterDynamicTabs] + : [...dynamicTabs, ...serviceTabs]; const changeTabsCallback: ChangeTabsCallback = useCallback( (newId, oldId, event) => { diff --git a/src/commons/sideContent/SideContentTypes.ts b/src/commons/sideContent/SideContentTypes.ts index 531717cc0e..870114fb1f 100644 --- a/src/commons/sideContent/SideContentTypes.ts +++ b/src/commons/sideContent/SideContentTypes.ts @@ -37,6 +37,8 @@ export enum SideContentType { upload = 'upload', } +export type SideContentTabId = SideContentType | string; + /** * @property label A string that will appear as the tooltip. * @@ -57,7 +59,7 @@ export type SideContentTab = { label: string; iconName: IconName; body: React.ReactElement | null; - id?: SideContentType; + id?: SideContentTabId; disabled?: boolean; }; @@ -93,12 +95,12 @@ export type SideContentState = { height?: number; dynamicTabs: SideContentTab[]; alerts: string[]; - selectedTab?: SideContentType; + selectedTab?: SideContentTabId; }; export type ChangeTabsCallback = ( - newId: SideContentType, - oldId: SideContentType, + newId: SideContentTabId, + oldId: SideContentTabId, event: React.MouseEvent, ) => void; @@ -106,5 +108,5 @@ export type SideContentDispatchProps = { /** * Call this function to cause the icon of the tab with the provided ID to flash */ - alertSideContent: (newId: SideContentType) => void; + alertSideContent: (newId: SideContentTabId) => void; }; diff --git a/src/features/conductor/Registry.ts b/src/features/conductor/Registry.ts new file mode 100644 index 0000000000..5d2ac2a051 --- /dev/null +++ b/src/features/conductor/Registry.ts @@ -0,0 +1,11 @@ +import type { ITabService } from '@sourceacademy/common-tabs'; +import type { PluginClass } from '@sourceacademy/conductor/conduit'; + +import AutoCompletePlugin from './AutocompletePlugin'; +import { TestPlugin } from './TestPlugin'; + +export type PluginRegistry = Map>; + +export const registry: PluginRegistry = new Map(); +registry.set('__autocomplete_plugin_web', AutoCompletePlugin); +registry.set('__web_test', TestPlugin); diff --git a/src/features/conductor/TestPlugin.ts b/src/features/conductor/TestPlugin.ts new file mode 100644 index 0000000000..c43255e4bd --- /dev/null +++ b/src/features/conductor/TestPlugin.ts @@ -0,0 +1,2 @@ +import { TestPlugin as TestPluginAbstractClass } from '@sourceacademy/web-test'; +export class TestPlugin extends TestPluginAbstractClass {} diff --git a/src/pages/academy/grading/subcomponents/GradingWorkspace.tsx b/src/pages/academy/grading/subcomponents/GradingWorkspace.tsx index 964aef234d..801337564c 100644 --- a/src/pages/academy/grading/subcomponents/GradingWorkspace.tsx +++ b/src/pages/academy/grading/subcomponents/GradingWorkspace.tsx @@ -38,6 +38,7 @@ import type { SideContentProps } from '../../../../commons/sideContent/SideConte import { useSideContent } from '../../../../commons/sideContent/SideContentHelper'; import { type SideContentTab, + type SideContentTabId, SideContentType, } from '../../../../commons/sideContent/SideContentTypes'; import Workspace, { type WorkspaceProps } from '../../../../commons/workspace/Workspace'; @@ -425,8 +426,8 @@ function GradingWorkspace(props: Props) { const sideContentProps: SideContentProps = { onChange: ( - newTabId: SideContentType, - prevTabId: SideContentType, + newTabId: SideContentTabId, + prevTabId: SideContentTabId, event: React.MouseEvent, ) => { if (newTabId === prevTabId) { diff --git a/src/pages/playground/PlaygroundTabs.tsx b/src/pages/playground/PlaygroundTabs.tsx index 7496ea8539..1d2ae958a9 100644 --- a/src/pages/playground/PlaygroundTabs.tsx +++ b/src/pages/playground/PlaygroundTabs.tsx @@ -8,14 +8,15 @@ import SideContentSubstVisualizer from 'src/commons/sideContent/content/SideCont import { type SideContentLocation, type SideContentTab, + type SideContentTabId, SideContentType, } from 'src/commons/sideContent/SideContentTypes'; -export const mobileOnlyTabIds: readonly SideContentType[] = [ +export const mobileOnlyTabIds: readonly SideContentTabId[] = [ SideContentType.mobileEditor, SideContentType.mobileEditorRun, ]; -export const desktopOnlyTabIds: readonly SideContentType[] = [SideContentType.introduction]; +export const desktopOnlyTabIds: readonly SideContentTabId[] = [SideContentType.introduction]; export const makeIntroductionTabFrom = (content: string): SideContentTab => ({ label: 'Introduction', diff --git a/yarn.lock b/yarn.lock index 1264fe5786..e1803972f5 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1373,7 +1373,7 @@ __metadata: languageName: node linkType: hard -"@blueprintjs/core@npm:^6.0.0, @blueprintjs/core@npm:^6.16.0": +"@blueprintjs/core@npm:^6.0.0": version: 6.16.0 resolution: "@blueprintjs/core@npm:6.16.0" dependencies: @@ -1400,7 +1400,34 @@ __metadata: languageName: node linkType: hard -"@blueprintjs/icons@npm:^6.0.0, @blueprintjs/icons@npm:^6.11.0": +"@blueprintjs/core@npm:^6.15.0": + version: 6.15.0 + resolution: "@blueprintjs/core@npm:6.15.0" + dependencies: + "@blueprintjs/colors": "npm:^5.1.16" + "@blueprintjs/icons": "npm:^6.10.0" + "@floating-ui/react": "npm:^0.27.13" + "@popperjs/core": "npm:^2.11.8" + classnames: "npm:^2.3.1" + normalize.css: "npm:^8.0.1" + react-popper: "npm:^2.3.0" + react-transition-group: "npm:^4.4.5" + tslib: "npm:~2.6.2" + peerDependencies: + "@types/react": 18 + react: 18 + react-dom: 18 + peerDependenciesMeta: + "@types/react": + optional: true + bin: + upgrade-blueprint-2.0.0-rename: scripts/upgrade-blueprint-2.0.0-rename.sh + upgrade-blueprint-3.0.0-rename: scripts/upgrade-blueprint-3.0.0-rename.sh + checksum: 10c0/f55c325d9eb5c4e8498bfaaeb1e0a2fd5c12c761e3d662a6698d32b94919b3c35fbdb5d38637cbaf692b54aef4b884474de5da733b33b0c965252da99ddec20f + languageName: node + linkType: hard + +"@blueprintjs/icons@npm:>=6.0.0, @blueprintjs/icons@npm:^6.0.0, @blueprintjs/icons@npm:^6.11.0": version: 6.11.0 resolution: "@blueprintjs/icons@npm:6.11.0" dependencies: @@ -1418,23 +1445,41 @@ __metadata: languageName: node linkType: hard +"@blueprintjs/icons@npm:^6.10.0": + version: 6.10.0 + resolution: "@blueprintjs/icons@npm:6.10.0" + dependencies: + change-case: "npm:^4.1.2" + classnames: "npm:^2.3.1" + tslib: "npm:~2.6.2" + peerDependencies: + "@types/react": 18 + react: 18 + react-dom: 18 + peerDependenciesMeta: + "@types/react": + optional: true + checksum: 10c0/1111739e69a1ca4273c988d75236e9f2e3cf5b297bda90a74568fcb8edf39eae71dad57ae5d892b303dff28f93e5bb17f575670c1a30245ccb8a458480c60484 + languageName: node + linkType: hard + "@blueprintjs/select@npm:^6.0.0": - version: 6.3.0 - resolution: "@blueprintjs/select@npm:6.3.0" + version: 6.2.1 + resolution: "@blueprintjs/select@npm:6.2.1" dependencies: "@blueprintjs/colors": "npm:^5.1.16" - "@blueprintjs/core": "npm:^6.16.0" - "@blueprintjs/icons": "npm:^6.11.0" + "@blueprintjs/core": "npm:^6.15.0" + "@blueprintjs/icons": "npm:^6.10.0" classnames: "npm:^2.3.1" tslib: "npm:~2.6.2" peerDependencies: - "@types/react": 18 || 19 - react: 18 || 19 - react-dom: 18 || 19 + "@types/react": 18 + react: 18 + react-dom: 18 peerDependenciesMeta: "@types/react": optional: true - checksum: 10c0/a5281ec67c439757c7596cc9eab5a87328c116fae0af5904eb7bfd04d43fc5d1da6d0bdaa4cd89bfbb80b8ffd0b8b89fd5a058d267909e847699ba9b88550cf5 + checksum: 10c0/8d9bd1b70ea96bf4b3daabe2aae4a8f85e72c6e539a2c06df2a24f82e06d2b85359826bdd64f6fa8ce0b5af727dda9b1324526194a0fe87b3a1ce27bd3c96f31 languageName: node linkType: hard @@ -2105,11 +2150,11 @@ __metadata: linkType: hard "@mantine/hooks@npm:^9.0.0": - version: 9.3.1 - resolution: "@mantine/hooks@npm:9.3.1" + version: 9.3.0 + resolution: "@mantine/hooks@npm:9.3.0" peerDependencies: react: ^19.2.0 - checksum: 10c0/f89cfccd01fae5c9d691fa8391741e769c0ed6930ddffafd353884afa8057cfdbea4747bab7847950195019d1e79d8398033841ea02398c27b07e10e150ab6c8 + checksum: 10c0/ed95a2551b34719d43d7b195cf05248498f20add736a2e4ec582b677e5a9efb53b562010df79dc9af7db087e76e9ac42192443c9d03f76963b5dba7dfc2e0a63 languageName: node linkType: hard @@ -2799,23 +2844,23 @@ __metadata: linkType: hard "@rsbuild/plugin-eslint@npm:^2.0.0": - version: 2.0.2 - resolution: "@rsbuild/plugin-eslint@npm:2.0.2" + version: 2.0.1 + resolution: "@rsbuild/plugin-eslint@npm:2.0.1" dependencies: - eslint-rspack-plugin: "npm:5.0.1" + eslint-rspack-plugin: "npm:5.0.0" peerDependencies: - "@rsbuild/core": ^1.0.0 || ^2.0.0 + "@rsbuild/core": ^1.0.0 || ^2.0.0-0 eslint: ^9.0.0 || ^10.0.0 peerDependenciesMeta: "@rsbuild/core": optional: true - checksum: 10c0/0cfd4b1106ebc5a78bc6e68c4c6c3101e3d67534ae99e3d92386627f916fa6b6e22796497e43287435ea2a208cd679863ac2fcb413599520bda0cae75dfd5acb + checksum: 10c0/f6f218bdd6aa9ab437b8c048b1f3a1ef2ff93a807f704dea72e16bb1a7420a227686138cc87483386c22482978a64005da6dfc95d376bb2cc0b152404cfe3efd languageName: node linkType: hard "@rsbuild/plugin-node-polyfill@npm:^1.4.4": - version: 1.4.6 - resolution: "@rsbuild/plugin-node-polyfill@npm:1.4.6" + version: 1.4.5 + resolution: "@rsbuild/plugin-node-polyfill@npm:1.4.5" dependencies: assert: "npm:^2.1.0" browserify-zlib: "npm:^0.2.0" @@ -2841,11 +2886,11 @@ __metadata: util: "npm:^0.12.5" vm-browserify: "npm:^1.1.2" peerDependencies: - "@rsbuild/core": ^1.0.0 || ^2.0.0 + "@rsbuild/core": ^1.0.0 || ^2.0.0-0 peerDependenciesMeta: "@rsbuild/core": optional: true - checksum: 10c0/7157ca2279b572f445e666a4da88a93fc454da756c1b3f70e697f7f2ac2b11fbf4bd68bf1c904d30dc1623fe83c9da5efcb9e1e4ad9f8e814afd67388ed5df18 + checksum: 10c0/d82b517f04656ef97cda5c3a380371f4f0a7ff193ee96a6a48fc2b49928b9d7e6ca4d162c07c8d1bc0546b5e5804ccbfda67c19799b4f2f3666eab69506a867c languageName: node linkType: hard @@ -2902,8 +2947,8 @@ __metadata: linkType: hard "@rsbuild/plugin-tailwindcss@npm:^2.0.0": - version: 2.0.2 - resolution: "@rsbuild/plugin-tailwindcss@npm:2.0.2" + version: 2.0.1 + resolution: "@rsbuild/plugin-tailwindcss@npm:2.0.1" dependencies: "@tailwindcss/webpack": "npm:^4.3.0" peerDependencies: @@ -2911,7 +2956,7 @@ __metadata: peerDependenciesMeta: "@rsbuild/core": optional: true - checksum: 10c0/d5c9b2c60d3711503bfa820b8e940e9aaf56bb480684ea86e3fb11756e9a8f520ac585fd3f2f153e4e48761dbe55e402f00dfc931e2888350e2da52afb095907 + checksum: 10c0/835490e4ca50391b1c9cf145418c81befe64e6bcc6831372a960d251006f5f09a8fbc172189178909f302a14f68b5ea809271bfdbbd46a4d30d0bab8cf47b1c1 languageName: node linkType: hard @@ -3065,73 +3110,73 @@ __metadata: languageName: node linkType: hard -"@sentry-internal/browser-utils@npm:10.57.0": - version: 10.57.0 - resolution: "@sentry-internal/browser-utils@npm:10.57.0" +"@sentry-internal/browser-utils@npm:10.56.0": + version: 10.56.0 + resolution: "@sentry-internal/browser-utils@npm:10.56.0" dependencies: - "@sentry/core": "npm:10.57.0" - checksum: 10c0/2594b1d73b8e4defdb1f74305fe2ebd5fcae99cdae10fd1a5a014ca940e8be7a117d3c47a65b9ac20dcf39fd5ef013dce886056e9a1c91b1828d4d5e3ad7ee4c + "@sentry/core": "npm:10.56.0" + checksum: 10c0/0fb3401c35b6c20975037c6439a4f8ae25429f9b1e5c61726b3179dbfc755e666ae9ccded3d747d142d0cbe42821b23c86c836c1c71e7a7a67641c95cec857a9 languageName: node linkType: hard -"@sentry-internal/feedback@npm:10.57.0": - version: 10.57.0 - resolution: "@sentry-internal/feedback@npm:10.57.0" +"@sentry-internal/feedback@npm:10.56.0": + version: 10.56.0 + resolution: "@sentry-internal/feedback@npm:10.56.0" dependencies: - "@sentry/core": "npm:10.57.0" - checksum: 10c0/5a0740cc77a44a30b883a68e3a8d763fdf65741f8cc454c3e0be4ac3321cdc9d3d9b61eb08d460253f3cdab510121474d2b1e5b9ce384691cb51eb1cb6e029b9 + "@sentry/core": "npm:10.56.0" + checksum: 10c0/f6b048f8fcbeca611837484b7f07b2e5cf5b9443b770e400277ec1dfb6ac6055853566fb54225801dadea3671b3d5c735fa8af5fde4b99a3820670c19d579d60 languageName: node linkType: hard -"@sentry-internal/replay-canvas@npm:10.57.0": - version: 10.57.0 - resolution: "@sentry-internal/replay-canvas@npm:10.57.0" +"@sentry-internal/replay-canvas@npm:10.56.0": + version: 10.56.0 + resolution: "@sentry-internal/replay-canvas@npm:10.56.0" dependencies: - "@sentry-internal/replay": "npm:10.57.0" - "@sentry/core": "npm:10.57.0" - checksum: 10c0/87e76c635434bb1ea3bf65efdd420026fc9f519ef1d67c4b1264ee872f85fcd99564cee67ae33f816867f628f543473952225f0f03ab6023c3af0b6e9358f1a1 + "@sentry-internal/replay": "npm:10.56.0" + "@sentry/core": "npm:10.56.0" + checksum: 10c0/9a94e5f6d27adcefd8a5a6414d84f06dce5e1840a68c568aabd9f893afb214c0c05d8bbc463e172af81fe2550e07f2464709eb8596ddf873ad0aec84c920ecf5 languageName: node linkType: hard -"@sentry-internal/replay@npm:10.57.0": - version: 10.57.0 - resolution: "@sentry-internal/replay@npm:10.57.0" +"@sentry-internal/replay@npm:10.56.0": + version: 10.56.0 + resolution: "@sentry-internal/replay@npm:10.56.0" dependencies: - "@sentry-internal/browser-utils": "npm:10.57.0" - "@sentry/core": "npm:10.57.0" - checksum: 10c0/b1961c1bc0b643439d94dfdf64a6e1aa4ba0b3b50aaafb07ce0579bc2033801894ac7d6602e493012a2266dd9ee1c279cdd3a7a240b0f04eb4254ffe6e535896 + "@sentry-internal/browser-utils": "npm:10.56.0" + "@sentry/core": "npm:10.56.0" + checksum: 10c0/2879b0aa900406747d7edb81d08bae27871f483a2b33f9523b0f3a09a309f353aa2cd650245090b654508a5644f51d5bee99f86e7f06f70f558c36452aba9265 languageName: node linkType: hard -"@sentry/browser@npm:10.57.0": - version: 10.57.0 - resolution: "@sentry/browser@npm:10.57.0" +"@sentry/browser@npm:10.56.0": + version: 10.56.0 + resolution: "@sentry/browser@npm:10.56.0" dependencies: - "@sentry-internal/browser-utils": "npm:10.57.0" - "@sentry-internal/feedback": "npm:10.57.0" - "@sentry-internal/replay": "npm:10.57.0" - "@sentry-internal/replay-canvas": "npm:10.57.0" - "@sentry/core": "npm:10.57.0" - checksum: 10c0/963a7a96a5067868a5868549869d69179f642d75efb9f0f15897b55c4f16e32fc10b9e525992552706ec7bb3382e0323d6ebb7cdbcb8d90ae81d7e52ebb3bd72 + "@sentry-internal/browser-utils": "npm:10.56.0" + "@sentry-internal/feedback": "npm:10.56.0" + "@sentry-internal/replay": "npm:10.56.0" + "@sentry-internal/replay-canvas": "npm:10.56.0" + "@sentry/core": "npm:10.56.0" + checksum: 10c0/52f5d7d34b2c0ee6fa9ce6f0e7e6567a18cb4958603147e4cbb872653f254f9a3c63a559831876f071536a8fb3d0238c219c1e87ee97b7656c23eab1b568463a languageName: node linkType: hard -"@sentry/core@npm:10.57.0": - version: 10.57.0 - resolution: "@sentry/core@npm:10.57.0" - checksum: 10c0/6ae4fc5a0881e3ca1ebf02441c796038fccef89e7bb7a3a38ca3f1e6c8f84153bd0d1d026dda4ff63a370461966e1ab37190ba1118b44cf19db15e030a37cf11 +"@sentry/core@npm:10.56.0": + version: 10.56.0 + resolution: "@sentry/core@npm:10.56.0" + checksum: 10c0/506fd5632923c44a2fb12964c7aaa5d100a83cd77a6c2053d8de2208d9164d0f36ae787d115537a8a2fe8a1bca8d9b08664f68f766dafb1b09360992fb8c34c9 languageName: node linkType: hard "@sentry/react@npm:^10.5.0": - version: 10.57.0 - resolution: "@sentry/react@npm:10.57.0" + version: 10.56.0 + resolution: "@sentry/react@npm:10.56.0" dependencies: - "@sentry/browser": "npm:10.57.0" - "@sentry/core": "npm:10.57.0" + "@sentry/browser": "npm:10.56.0" + "@sentry/core": "npm:10.56.0" peerDependencies: react: ^16.14.0 || 17.x || 18.x || 19.x - checksum: 10c0/01197908d8f4cd9b5326781dc56aa587aa61f79b896a37aa8c990bd6e10c671b51cfddf0252be20f4a4c3afcbb50fe9e3523ff7d80afd68141a7fc85749b13a8 + checksum: 10c0/8ac9336186f6070c9539a49500226090bb659e9f5c222549f94e3fd937bba83468944bdb355ea4b394cd2534aaf4e9072e38f2ebe264db0d82943dece0ab5da8 languageName: node linkType: hard @@ -3155,6 +3200,16 @@ __metadata: languageName: node linkType: hard +"@sourceacademy/common-tabs@file:../plugins/src/common/tabs::locator=frontend%40workspace%3A.": + version: 0.0.1 + resolution: "@sourceacademy/common-tabs@file:../plugins/src/common/tabs#../plugins/src/common/tabs::hash=c05497&locator=frontend%40workspace%3A." + dependencies: + "@blueprintjs/icons": "npm:>=6.0.0" + "@types/react": "npm:^19.2.17" + checksum: 10c0/0c57f8e616756f5f9ee818c10e2f127b89b5302f8ac15e3ed14c6612f3fe65910397482e5206929a3be5382b753cb96af58739c821d702516b02c361ca188e7c + languageName: node + linkType: hard + "@sourceacademy/conductor@https://github.com/source-academy/conductor.git#0.4.0": version: 0.4.0 resolution: "@sourceacademy/conductor@https://github.com/source-academy/conductor.git#commit=00a0d5fdce269d62a0feb33230300db27121be05" @@ -3198,6 +3253,17 @@ __metadata: languageName: node linkType: hard +"@sourceacademy/web-test@file:..\\plugins\\src\\web\\test::locator=frontend%40workspace%3A.": + version: 0.0.1 + resolution: "@sourceacademy/web-test@file:../plugins/src/web/test#../plugins/src/web/test::hash=107271&locator=frontend%40workspace%3A." + dependencies: + "@types/react": "npm:^19.2.17" + react: "npm:^19.2.7" + react-dom: "npm:^19.2.7" + checksum: 10c0/838a8d3e4f527f668ce3779e8d95d681e593301455abe6287cd6cd56ef245b67d0576ef191ef2c79af28b2f2c9ffff45f90349fd53680422b65c21a0032a3968 + languageName: node + linkType: hard + "@standard-schema/spec@npm:^1.1.0": version: 1.1.0 resolution: "@standard-schema/spec@npm:1.1.0" @@ -3373,106 +3439,106 @@ __metadata: languageName: node linkType: hard -"@swc/core-darwin-arm64@npm:1.15.41": - version: 1.15.41 - resolution: "@swc/core-darwin-arm64@npm:1.15.41" +"@swc/core-darwin-arm64@npm:1.15.40": + version: 1.15.40 + resolution: "@swc/core-darwin-arm64@npm:1.15.40" conditions: os=darwin & cpu=arm64 languageName: node linkType: hard -"@swc/core-darwin-x64@npm:1.15.41": - version: 1.15.41 - resolution: "@swc/core-darwin-x64@npm:1.15.41" +"@swc/core-darwin-x64@npm:1.15.40": + version: 1.15.40 + resolution: "@swc/core-darwin-x64@npm:1.15.40" conditions: os=darwin & cpu=x64 languageName: node linkType: hard -"@swc/core-linux-arm-gnueabihf@npm:1.15.41": - version: 1.15.41 - resolution: "@swc/core-linux-arm-gnueabihf@npm:1.15.41" +"@swc/core-linux-arm-gnueabihf@npm:1.15.40": + version: 1.15.40 + resolution: "@swc/core-linux-arm-gnueabihf@npm:1.15.40" conditions: os=linux & cpu=arm languageName: node linkType: hard -"@swc/core-linux-arm64-gnu@npm:1.15.41": - version: 1.15.41 - resolution: "@swc/core-linux-arm64-gnu@npm:1.15.41" +"@swc/core-linux-arm64-gnu@npm:1.15.40": + version: 1.15.40 + resolution: "@swc/core-linux-arm64-gnu@npm:1.15.40" conditions: os=linux & cpu=arm64 & libc=glibc languageName: node linkType: hard -"@swc/core-linux-arm64-musl@npm:1.15.41": - version: 1.15.41 - resolution: "@swc/core-linux-arm64-musl@npm:1.15.41" +"@swc/core-linux-arm64-musl@npm:1.15.40": + version: 1.15.40 + resolution: "@swc/core-linux-arm64-musl@npm:1.15.40" conditions: os=linux & cpu=arm64 & libc=musl languageName: node linkType: hard -"@swc/core-linux-ppc64-gnu@npm:1.15.41": - version: 1.15.41 - resolution: "@swc/core-linux-ppc64-gnu@npm:1.15.41" +"@swc/core-linux-ppc64-gnu@npm:1.15.40": + version: 1.15.40 + resolution: "@swc/core-linux-ppc64-gnu@npm:1.15.40" conditions: os=linux & cpu=ppc64 & libc=glibc languageName: node linkType: hard -"@swc/core-linux-s390x-gnu@npm:1.15.41": - version: 1.15.41 - resolution: "@swc/core-linux-s390x-gnu@npm:1.15.41" +"@swc/core-linux-s390x-gnu@npm:1.15.40": + version: 1.15.40 + resolution: "@swc/core-linux-s390x-gnu@npm:1.15.40" conditions: os=linux & cpu=s390x & libc=glibc languageName: node linkType: hard -"@swc/core-linux-x64-gnu@npm:1.15.41": - version: 1.15.41 - resolution: "@swc/core-linux-x64-gnu@npm:1.15.41" +"@swc/core-linux-x64-gnu@npm:1.15.40": + version: 1.15.40 + resolution: "@swc/core-linux-x64-gnu@npm:1.15.40" conditions: os=linux & cpu=x64 & libc=glibc languageName: node linkType: hard -"@swc/core-linux-x64-musl@npm:1.15.41": - version: 1.15.41 - resolution: "@swc/core-linux-x64-musl@npm:1.15.41" +"@swc/core-linux-x64-musl@npm:1.15.40": + version: 1.15.40 + resolution: "@swc/core-linux-x64-musl@npm:1.15.40" conditions: os=linux & cpu=x64 & libc=musl languageName: node linkType: hard -"@swc/core-win32-arm64-msvc@npm:1.15.41": - version: 1.15.41 - resolution: "@swc/core-win32-arm64-msvc@npm:1.15.41" +"@swc/core-win32-arm64-msvc@npm:1.15.40": + version: 1.15.40 + resolution: "@swc/core-win32-arm64-msvc@npm:1.15.40" conditions: os=win32 & cpu=arm64 languageName: node linkType: hard -"@swc/core-win32-ia32-msvc@npm:1.15.41": - version: 1.15.41 - resolution: "@swc/core-win32-ia32-msvc@npm:1.15.41" +"@swc/core-win32-ia32-msvc@npm:1.15.40": + version: 1.15.40 + resolution: "@swc/core-win32-ia32-msvc@npm:1.15.40" conditions: os=win32 & cpu=ia32 languageName: node linkType: hard -"@swc/core-win32-x64-msvc@npm:1.15.41": - version: 1.15.41 - resolution: "@swc/core-win32-x64-msvc@npm:1.15.41" +"@swc/core-win32-x64-msvc@npm:1.15.40": + version: 1.15.40 + resolution: "@swc/core-win32-x64-msvc@npm:1.15.40" conditions: os=win32 & cpu=x64 languageName: node linkType: hard "@swc/core@npm:^1.11.22": - version: 1.15.41 - resolution: "@swc/core@npm:1.15.41" - dependencies: - "@swc/core-darwin-arm64": "npm:1.15.41" - "@swc/core-darwin-x64": "npm:1.15.41" - "@swc/core-linux-arm-gnueabihf": "npm:1.15.41" - "@swc/core-linux-arm64-gnu": "npm:1.15.41" - "@swc/core-linux-arm64-musl": "npm:1.15.41" - "@swc/core-linux-ppc64-gnu": "npm:1.15.41" - "@swc/core-linux-s390x-gnu": "npm:1.15.41" - "@swc/core-linux-x64-gnu": "npm:1.15.41" - "@swc/core-linux-x64-musl": "npm:1.15.41" - "@swc/core-win32-arm64-msvc": "npm:1.15.41" - "@swc/core-win32-ia32-msvc": "npm:1.15.41" - "@swc/core-win32-x64-msvc": "npm:1.15.41" + version: 1.15.40 + resolution: "@swc/core@npm:1.15.40" + dependencies: + "@swc/core-darwin-arm64": "npm:1.15.40" + "@swc/core-darwin-x64": "npm:1.15.40" + "@swc/core-linux-arm-gnueabihf": "npm:1.15.40" + "@swc/core-linux-arm64-gnu": "npm:1.15.40" + "@swc/core-linux-arm64-musl": "npm:1.15.40" + "@swc/core-linux-ppc64-gnu": "npm:1.15.40" + "@swc/core-linux-s390x-gnu": "npm:1.15.40" + "@swc/core-linux-x64-gnu": "npm:1.15.40" + "@swc/core-linux-x64-musl": "npm:1.15.40" + "@swc/core-win32-arm64-msvc": "npm:1.15.40" + "@swc/core-win32-ia32-msvc": "npm:1.15.40" + "@swc/core-win32-x64-msvc": "npm:1.15.40" "@swc/counter": "npm:^0.1.3" "@swc/types": "npm:^0.1.26" peerDependencies: @@ -3505,7 +3571,7 @@ __metadata: peerDependenciesMeta: "@swc/helpers": optional: true - checksum: 10c0/18eb11efdef627e34138c1d0042c23677dc9c41867032299e8fbfb53c0431fb91418ae9e74cc1f1b83caf2ea42a6558347dd8f4efb8ab592c6ef42094b80e419 + checksum: 10c0/852a8940b307b08ea6d628e01bb0855fba6c60e59c8c1d4fa0bca9700e61106bb05939a721bf864f2f6d91c502d43cd938cb7f12e56f78eb56bc4f26c49fd8b7 languageName: node linkType: hard @@ -4111,7 +4177,7 @@ __metadata: languageName: node linkType: hard -"@types/react@npm:*, @types/react@npm:^19.1.8": +"@types/react@npm:*, @types/react@npm:^19.1.8, @types/react@npm:^19.2.17": version: 19.2.17 resolution: "@types/react@npm:19.2.17" dependencies: @@ -4187,105 +4253,105 @@ __metadata: languageName: node linkType: hard -"@typescript-eslint/eslint-plugin@npm:8.61.0": - version: 8.61.0 - resolution: "@typescript-eslint/eslint-plugin@npm:8.61.0" +"@typescript-eslint/eslint-plugin@npm:8.60.1": + version: 8.60.1 + resolution: "@typescript-eslint/eslint-plugin@npm:8.60.1" dependencies: "@eslint-community/regexpp": "npm:^4.12.2" - "@typescript-eslint/scope-manager": "npm:8.61.0" - "@typescript-eslint/type-utils": "npm:8.61.0" - "@typescript-eslint/utils": "npm:8.61.0" - "@typescript-eslint/visitor-keys": "npm:8.61.0" + "@typescript-eslint/scope-manager": "npm:8.60.1" + "@typescript-eslint/type-utils": "npm:8.60.1" + "@typescript-eslint/utils": "npm:8.60.1" + "@typescript-eslint/visitor-keys": "npm:8.60.1" ignore: "npm:^7.0.5" natural-compare: "npm:^1.4.0" ts-api-utils: "npm:^2.5.0" peerDependencies: - "@typescript-eslint/parser": ^8.61.0 + "@typescript-eslint/parser": ^8.60.1 eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 typescript: ">=4.8.4 <6.1.0" - checksum: 10c0/1141253e18424a9a21d253dcf28e166894b6b914f2138b3e016144e451385f8e23f0f02028c7bd2d21c81953c52e478657aa9e5888cd0bdffdb8d68aab736878 + checksum: 10c0/de9f9ab9801970c8c96f342b94661e993e8a66f90a36fc4501a7238585712900a2f1f5c7c805adb1214f98b478a072f0aa590e22dd4ed36231dcabde3f6c7b2f languageName: node linkType: hard -"@typescript-eslint/parser@npm:8.61.0": - version: 8.61.0 - resolution: "@typescript-eslint/parser@npm:8.61.0" +"@typescript-eslint/parser@npm:8.60.1": + version: 8.60.1 + resolution: "@typescript-eslint/parser@npm:8.60.1" dependencies: - "@typescript-eslint/scope-manager": "npm:8.61.0" - "@typescript-eslint/types": "npm:8.61.0" - "@typescript-eslint/typescript-estree": "npm:8.61.0" - "@typescript-eslint/visitor-keys": "npm:8.61.0" + "@typescript-eslint/scope-manager": "npm:8.60.1" + "@typescript-eslint/types": "npm:8.60.1" + "@typescript-eslint/typescript-estree": "npm:8.60.1" + "@typescript-eslint/visitor-keys": "npm:8.60.1" debug: "npm:^4.4.3" peerDependencies: eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 typescript: ">=4.8.4 <6.1.0" - checksum: 10c0/39b122ab20a3b5fbd4e66874f60917b37c49f32eb987531797561d2f96b443e81546f1c9d03d44d37dede89098276e5d8d0f05c1e5f9e1b998f8cf6c24e8e5e7 + checksum: 10c0/8bc9ecccac411cda8f6bc38fce2427639071a41f44594b047b40a4a50fd40959797acd373b87ab40e4f4b49e9069d42e1480d91e100800d5fb5e6ec6e4afba71 languageName: node linkType: hard -"@typescript-eslint/project-service@npm:8.61.0": - version: 8.61.0 - resolution: "@typescript-eslint/project-service@npm:8.61.0" +"@typescript-eslint/project-service@npm:8.60.1": + version: 8.60.1 + resolution: "@typescript-eslint/project-service@npm:8.60.1" dependencies: - "@typescript-eslint/tsconfig-utils": "npm:^8.61.0" - "@typescript-eslint/types": "npm:^8.61.0" + "@typescript-eslint/tsconfig-utils": "npm:^8.60.1" + "@typescript-eslint/types": "npm:^8.60.1" debug: "npm:^4.4.3" peerDependencies: typescript: ">=4.8.4 <6.1.0" - checksum: 10c0/8ff86b93bfcf103a42e8e996e11c46ded83da07d3a0bc8bd9ec4d536116d7f6253a404786510ab13847e69d6e185b17d15d7140075c26966e9b4f85c03296f21 + checksum: 10c0/f5a61b7f2c90d07b9f89b8d0e4bb5b9a62ab1fc08060b1f6e04793a0ff9bcaa4160afe7662d8027faa7a509cec1354f9178e2e598cae7a66c55a038c70fa0274 languageName: node linkType: hard -"@typescript-eslint/scope-manager@npm:8.61.0": - version: 8.61.0 - resolution: "@typescript-eslint/scope-manager@npm:8.61.0" +"@typescript-eslint/scope-manager@npm:8.60.1": + version: 8.60.1 + resolution: "@typescript-eslint/scope-manager@npm:8.60.1" dependencies: - "@typescript-eslint/types": "npm:8.61.0" - "@typescript-eslint/visitor-keys": "npm:8.61.0" - checksum: 10c0/76cdf1c181ebbc706ddc8b2366e8ebfda529c13d82ff10c0797c96c0b38dd82f6471b24995f58ac267194a753b23d77452d925dd615b1e651922ddbe6e451c6b + "@typescript-eslint/types": "npm:8.60.1" + "@typescript-eslint/visitor-keys": "npm:8.60.1" + checksum: 10c0/d9ead95aca27614ccfc160e5487480fc7c0de2e2e07716c5e2a56168f21adfa5124f33f579e7ff0c12896c61b59eb8ce50875c810fec2532a777ead0b103bccd languageName: node linkType: hard -"@typescript-eslint/tsconfig-utils@npm:8.61.0, @typescript-eslint/tsconfig-utils@npm:^8.61.0": - version: 8.61.0 - resolution: "@typescript-eslint/tsconfig-utils@npm:8.61.0" +"@typescript-eslint/tsconfig-utils@npm:8.60.1, @typescript-eslint/tsconfig-utils@npm:^8.60.1": + version: 8.60.1 + resolution: "@typescript-eslint/tsconfig-utils@npm:8.60.1" peerDependencies: typescript: ">=4.8.4 <6.1.0" - checksum: 10c0/b498675f14ef90a5730de7c58388eb2522085a56c3fcad42ad9f89320b96221eafb5b4f9650375f29092025153d03533e3f23ea8f45ce3bc95a57593059edef3 + checksum: 10c0/231d6c6ef0b305d5b007ce89af11c5871c14a5e3be43d1c131100f60053783169c1ce3133af767b8874bce6cc20ece1d2501c2ef315f467ecdc04e8acdd0dc9c languageName: node linkType: hard -"@typescript-eslint/type-utils@npm:8.61.0": - version: 8.61.0 - resolution: "@typescript-eslint/type-utils@npm:8.61.0" +"@typescript-eslint/type-utils@npm:8.60.1": + version: 8.60.1 + resolution: "@typescript-eslint/type-utils@npm:8.60.1" dependencies: - "@typescript-eslint/types": "npm:8.61.0" - "@typescript-eslint/typescript-estree": "npm:8.61.0" - "@typescript-eslint/utils": "npm:8.61.0" + "@typescript-eslint/types": "npm:8.60.1" + "@typescript-eslint/typescript-estree": "npm:8.60.1" + "@typescript-eslint/utils": "npm:8.60.1" debug: "npm:^4.4.3" ts-api-utils: "npm:^2.5.0" peerDependencies: eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 typescript: ">=4.8.4 <6.1.0" - checksum: 10c0/6347f451301ca7089500fe6eb3b98e5efd769e56ffda07eb735130fd209b9053c02e952b6fda7a15acf7851fb63f11fc50166ba8bd90513480732c599644b36b + checksum: 10c0/916d354fd22a2296abe0c618f89574ba6ed363b841bcbcbb662a53deaccd9bc644f253e7134d12f506d75cb574bbbc3e4113f253045b404e8a17962004e42f1d languageName: node linkType: hard -"@typescript-eslint/types@npm:8.61.0, @typescript-eslint/types@npm:^8.61.0": - version: 8.61.0 - resolution: "@typescript-eslint/types@npm:8.61.0" - checksum: 10c0/c19407d66fb5ad26e2670cd272bee91d150087d917752422257759e17920220af27cd54593205e9726367a440a237bf8d27ed805cae0b282a79172161f007207 +"@typescript-eslint/types@npm:8.60.1, @typescript-eslint/types@npm:^8.60.1": + version: 8.60.1 + resolution: "@typescript-eslint/types@npm:8.60.1" + checksum: 10c0/44308007e090ae1ac9cfdc5c2089cf1a82601298f69dd4835f62549e3d36886d41ecb1f84b490603382657481ca4e2ff23de49b97ad09d199dc65ce6c2e00b22 languageName: node linkType: hard -"@typescript-eslint/typescript-estree@npm:8.61.0": - version: 8.61.0 - resolution: "@typescript-eslint/typescript-estree@npm:8.61.0" +"@typescript-eslint/typescript-estree@npm:8.60.1": + version: 8.60.1 + resolution: "@typescript-eslint/typescript-estree@npm:8.60.1" dependencies: - "@typescript-eslint/project-service": "npm:8.61.0" - "@typescript-eslint/tsconfig-utils": "npm:8.61.0" - "@typescript-eslint/types": "npm:8.61.0" - "@typescript-eslint/visitor-keys": "npm:8.61.0" + "@typescript-eslint/project-service": "npm:8.60.1" + "@typescript-eslint/tsconfig-utils": "npm:8.60.1" + "@typescript-eslint/types": "npm:8.60.1" + "@typescript-eslint/visitor-keys": "npm:8.60.1" debug: "npm:^4.4.3" minimatch: "npm:^10.2.2" semver: "npm:^7.7.3" @@ -4293,32 +4359,32 @@ __metadata: ts-api-utils: "npm:^2.5.0" peerDependencies: typescript: ">=4.8.4 <6.1.0" - checksum: 10c0/460819feeca826bfd895f821a5008c3eaa79b9495259641976fdc6ec319a7e9587bc28603437ea3d9a10c3b28037f1dea883cbe8d2858616dd33847e8db2179e + checksum: 10c0/76274d3974fd56675df71b010a2b6799a886537625228f89150fcb4563597eb619be4a22937cacacb0bb20b66c11b03e04f913fb6b44790ce63a7d070f27d3aa languageName: node linkType: hard -"@typescript-eslint/utils@npm:8.61.0": - version: 8.61.0 - resolution: "@typescript-eslint/utils@npm:8.61.0" +"@typescript-eslint/utils@npm:8.60.1": + version: 8.60.1 + resolution: "@typescript-eslint/utils@npm:8.60.1" dependencies: "@eslint-community/eslint-utils": "npm:^4.9.1" - "@typescript-eslint/scope-manager": "npm:8.61.0" - "@typescript-eslint/types": "npm:8.61.0" - "@typescript-eslint/typescript-estree": "npm:8.61.0" + "@typescript-eslint/scope-manager": "npm:8.60.1" + "@typescript-eslint/types": "npm:8.60.1" + "@typescript-eslint/typescript-estree": "npm:8.60.1" peerDependencies: eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 typescript: ">=4.8.4 <6.1.0" - checksum: 10c0/f7b2241fc4defd40107243642e26697193707be12af1552c60bc414e71df1285c9cdff429f913b30ed08ae87a7e6e13388eaf05c1be5fb8310f6a63a6c4f7f73 + checksum: 10c0/24777b47e23f930df5e0a0858e2979dbc44597d52e7ad237d2d764a433ac214ac00c0f7d0245ce9a54eb31900d261e305dc8a77d31efbb73bd7523c0ab075299 languageName: node linkType: hard -"@typescript-eslint/visitor-keys@npm:8.61.0": - version: 8.61.0 - resolution: "@typescript-eslint/visitor-keys@npm:8.61.0" +"@typescript-eslint/visitor-keys@npm:8.60.1": + version: 8.60.1 + resolution: "@typescript-eslint/visitor-keys@npm:8.60.1" dependencies: - "@typescript-eslint/types": "npm:8.61.0" + "@typescript-eslint/types": "npm:8.60.1" eslint-visitor-keys: "npm:^5.0.0" - checksum: 10c0/5b656aed426a92dfc9a481f0bf535ceb47321303f476f32ba979f73423c739b51d7a5ad76c81d7be9df0a9beb361f4a11ff530dd86d59f41b89bb5f09af7be9f + checksum: 10c0/d9831624c0dde1655a83f3e10b85fe3655ec015fd57cac9295bf3ad302ef30736eb58417b1d9a5c8639a8b05b665f9acc6bcc34f9def386846ae8d6833a5e3ce languageName: node linkType: hard @@ -6706,9 +6772,9 @@ __metadata: languageName: node linkType: hard -"eslint-rspack-plugin@npm:5.0.1": - version: 5.0.1 - resolution: "eslint-rspack-plugin@npm:5.0.1" +"eslint-rspack-plugin@npm:5.0.0": + version: 5.0.0 + resolution: "eslint-rspack-plugin@npm:5.0.0" dependencies: micromatch: "npm:^4.0.8" normalize-path: "npm:^3.0.0" @@ -6719,7 +6785,7 @@ __metadata: peerDependenciesMeta: "@rspack/core": optional: true - checksum: 10c0/39e131b3150487787abba33b410a1a9dd260fdf15fdb2072ff8ce1f6ed1368adfedfcd2835702b1439d54ee497094a079a10a551c37198d713a1d1b1010407b1 + checksum: 10c0/c9e3b3155bc345dd44ab438d6a698a60aa2c55796ee7e0e7bac65eadc68eb755411bc378ee502d2700f873f18b05b15c1d5fecc96d1e4366326da4f32190164e languageName: node linkType: hard @@ -7218,11 +7284,13 @@ __metadata: "@sentry/react": "npm:^10.5.0" "@sourceacademy/autocomplete": "github:source-academy/autocomplete#e669d9ed98753350a3c8433a92985227eb789663" "@sourceacademy/c-slang": "npm:^1.0.21" + "@sourceacademy/common-tabs": "file:../plugins/src/common/tabs" "@sourceacademy/conductor": "https://github.com/source-academy/conductor.git#0.4.0" "@sourceacademy/language-directory": "https://github.com/source-academy/language-directory.git#0.0.6" "@sourceacademy/plugin-directory": "https://github.com/source-academy/plugin-directory.git#0.0.2" "@sourceacademy/sharedb-ace": "npm:2.1.1" "@sourceacademy/sling-client": "npm:^0.1.0" + "@sourceacademy/web-test": "file:..\\plugins\\src\\web\\test" "@svgr/webpack": "npm:^8.0.0" "@swc/core": "npm:^1.11.22" "@szhsin/react-menu": "npm:^4.0.0" @@ -10849,11 +10917,11 @@ __metadata: linkType: hard "prettier@npm:^3.3.3": - version: 3.8.4 - resolution: "prettier@npm:3.8.4" + version: 3.8.3 + resolution: "prettier@npm:3.8.3" bin: prettier: bin/prettier.cjs - checksum: 10c0/b90a0cbe75b88ac0af9c13fe0f359bd19926fabccd88483227b21f71f0c1cc42da056fc1ac3a361e665577c568371d5ccfb2c62c31c8a1186f8d1bd531a063e9 + checksum: 10c0/754816fd7593eb80f6376d7476d463e832c38a12f32775a82683adb6e35b772b1f484d65f19401507b983a8c8a7cd5a4a9f12006bd56491e8f35503473f77473 languageName: node linkType: hard @@ -11113,7 +11181,7 @@ __metadata: languageName: node linkType: hard -"react-dom@npm:^19.2.4": +"react-dom@npm:^19.2.4, react-dom@npm:^19.2.7": version: 19.2.7 resolution: "react-dom@npm:19.2.7" dependencies: @@ -11221,8 +11289,8 @@ __metadata: linkType: hard "react-konva@npm:^19.0.7": - version: 19.2.5 - resolution: "react-konva@npm:19.2.5" + version: 19.2.4 + resolution: "react-konva@npm:19.2.4" dependencies: "@types/react-reconciler": "npm:^0.33.0" its-fine: "npm:^2.0.0" @@ -11232,7 +11300,7 @@ __metadata: konva: ^8.0.1 || ^7.2.5 || ^9.0.0 || ^10.0.0 react: ^19.2.0 react-dom: ^19.2.0 - checksum: 10c0/6f222c47cc4c105a3f4b662a9a86a31983c77bf254aebac9ca21c4e0bf7d3f78a6c2b0c6c2cf23d44fbded1260c69e2a90766ef4eeec85d19457f8aebd978ad1 + checksum: 10c0/9698fd0799eb17aafab04540bbb46018a9bcdfb7fe26b9a072201c7db43c1625c742fc1a70fa59e7c2fec6c7604a2ebfb3f266d607bfb3ffbae1ea3447223927 languageName: node linkType: hard @@ -11468,7 +11536,7 @@ __metadata: languageName: node linkType: hard -"react@npm:^19.2.4": +"react@npm:^19.2.4, react@npm:^19.2.7": version: 19.2.7 resolution: "react@npm:19.2.7" checksum: 10c0/0bd0e2f1bbd4ba97561c6597bf8a5fec05e6476fe61e165c1065598d16668efc6715205599c94d3ddd49d36cb0f21cbf1b9bcc18ee840b805ce222c3e8d558ac @@ -13465,17 +13533,17 @@ __metadata: linkType: hard "typescript-eslint@npm:^8.1.0": - version: 8.61.0 - resolution: "typescript-eslint@npm:8.61.0" + version: 8.60.1 + resolution: "typescript-eslint@npm:8.60.1" dependencies: - "@typescript-eslint/eslint-plugin": "npm:8.61.0" - "@typescript-eslint/parser": "npm:8.61.0" - "@typescript-eslint/typescript-estree": "npm:8.61.0" - "@typescript-eslint/utils": "npm:8.61.0" + "@typescript-eslint/eslint-plugin": "npm:8.60.1" + "@typescript-eslint/parser": "npm:8.60.1" + "@typescript-eslint/typescript-estree": "npm:8.60.1" + "@typescript-eslint/utils": "npm:8.60.1" peerDependencies: eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 typescript: ">=4.8.4 <6.1.0" - checksum: 10c0/172ba723c7ea07e5b22541ca3e8eb1b5e0b837e21278a915a293a2d5e7cac7ce9f0ad73cd61a3cdec98a511c76586eb3865d1581f2acc0b691e007703d4764b2 + checksum: 10c0/75a42e14b4a7446dd9ad992422135f696e0af58d7c0f64ff2d9f157f1df7bac6a089fa7a35454d2393eadd329e602c0002c07043bbcf4906f7007e45e783b54e languageName: node linkType: hard