Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,8 @@
"use-deep-compare-effect": "^1.8.1",
"use-keyboard-shortcuts": "^2.2.2",
"visjs-network": "^4.24.11",
"yaml": "^2.0.1"
"yaml": "^2.0.1",
"zod": "^4.2.1"
},
"devDependencies": {
"@eslint/js": "^9.18.0",
Expand Down
23 changes: 11 additions & 12 deletions src/components/FullPlayground.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,6 @@ import { ShareLoader } from "./ShareLoader";
import { ValidateButton } from "./ValidationButton";
import { Panel, useSummaryStyles } from "./panels/base/common";
import { ReflexedPanelDisplay } from "./panels/base/reflexed";
import { PlaygroundPanelLocation } from "./panels/panels";
import { ProblemsPanel, ProblemsSummary } from "./panels/problems";
import { TerminalPanel, TerminalSummary } from "./panels/terminal";
import { ValidationPanel, ValidationSummary } from "./panels/validation";
Expand Down Expand Up @@ -1104,31 +1103,31 @@ const TabLabelWithCount = (props: {
);
};

const panels: Panel<PlaygroundPanelLocation>[] = [
const panels: Panel[] = [
{
id: "problems",
summary: ProblemsSummary,
content: ProblemsPanel,
Summary: ProblemsSummary,
Content: ProblemsPanel,
},
{
id: "watches",
summary: WatchesSummary,
content: WatchesPanel,
Summary: WatchesSummary,
Content: WatchesPanel,
},
{
id: "visualizer",
summary: VisualizerSummary,
content: VisualizerPanel,
Summary: VisualizerSummary,
Content: VisualizerPanel,
},
{
id: "validation",
summary: ValidationSummary,
content: ValidationPanel,
Summary: ValidationSummary,
Content: ValidationPanel,
},
{
id: "terminal",
summary: TerminalSummary,
content: TerminalPanel,
Summary: TerminalSummary,
Content: TerminalPanel,
},
];

Expand Down
16 changes: 9 additions & 7 deletions src/components/panels/base/common.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
import { type ReactNode } from "react";
import { createStyles, makeStyles, Theme } from "@material-ui/core/styles";
import "react-reflex/styles.css";
import { DataStore } from "../../../services/datastore";
import { Services } from "../../../services/services";
import { ReflexedPanelLocation } from "../types";

/**
* Panel defines a single panel found in the panel display component.
*/
export interface Panel<L extends string> {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Most of these changes are un-genericking all of these Ls. We only have a single instance of the ReflexedPanel top-level component, so there's no reason to propagate all of these generics through.

export interface Panel {
/**
* id is the unique ID for the panel. Must be stable across loads.
*/
Expand All @@ -15,29 +17,29 @@ export interface Panel<L extends string> {
/**
* summary is the React tag to render for displaying the summary of the panel.
*/
summary: (props: PanelSummaryProps<L>) => JSX.Element;
Summary: (props: PanelSummaryProps) => ReactNode;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Capsing here means we don't have to do a rename elsewhere.


/**
* content is the React tag to render for displaying the contents of the panel.
*/
content: (props: PanelProps<L>) => JSX.Element;
Content: (props: PanelProps) => ReactNode;
}

/**
* PanelProps are the props passed to all panels content tags.
*/
export interface PanelProps<L extends string> {
export interface PanelProps {
datastore: DataStore;
services: Services;
location: L;
location: ReflexedPanelLocation;
}

/**
* PanelSummaryProps are the props passed to all panel summary tags.
*/
export interface PanelSummaryProps<L extends string> {
export interface PanelSummaryProps {
services: Services;
location: L;
location: ReflexedPanelLocation;
}

/**
Expand Down
52 changes: 21 additions & 31 deletions src/components/panels/base/components.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,16 @@ import { DataStore } from "../../../services/datastore";
import { Services } from "../../../services/services";
import { Panel, useSummaryStyles } from "./common";
import { LocationData, PanelsCoordinator } from "./coordinator";
import { ReflexedPanelLocation } from "../types";
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the non-generic type. I'll call out its definition later.


export const SUMMARY_BAR_HEIGHT = 48; // Pixels

/**
* PanelSummaryBar is the summary bar displayed when a panel display is closed.
*/
export function PanelSummaryBar<L extends string>(props: {
location: L;
coordinator: PanelsCoordinator<L>;
export function PanelSummaryBar(props: {
location: ReflexedPanelLocation;
coordinator: PanelsCoordinator;
services: Services;
disabled?: boolean | undefined;
overrideSummaryDisplay?: ReactNode;
Expand All @@ -45,10 +46,10 @@ export function PanelSummaryBar<L extends string>(props: {
>
{props.overrideSummaryDisplay !== undefined &&
props.overrideSummaryDisplay}
{panels.map((panel: Panel<L>) => {
{panels.map((panel: Panel) => {
// NOTE: Using this as a tag here is important for React's state system. Otherwise,
// it'll run hooks outside of the normal flow, which breaks things.
const Summary = panel.summary;
const Summary = panel.Summary;
return (
<Button
key={panel.id}
Expand Down Expand Up @@ -114,23 +115,20 @@ const TOOLBAR_HEIGHT = 48; // Pixels
/**
* PanelDisplay displays the panels in a specific location.
*/
export function PanelDisplay<L extends string>(
props: {
location: L;
coordinator: PanelsCoordinator<L>;
datastore: DataStore;
services: Services;
} & {
dimensions?: { width: number; height: number };
},
Comment on lines -118 to -125
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I also flattened all of these types for the sake of simplicity.

) {
export function PanelDisplay(props: {
location: ReflexedPanelLocation;
coordinator: PanelsCoordinator;
datastore: DataStore;
services: Services;
dimensions?: { width: number; height: number };
}) {
const classes = useStyles();
const coordinator = props.coordinator;

const currentTabName = coordinator.getActivePanel(props.location)?.id || "";

const handleChangeTab = useCallback(
(_event: React.ChangeEvent<object>, selectedPanelId: string) => {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unused so why bother

(_event: object, selectedPanelId: string) => {
coordinator.setActivePanel(selectedPanelId, props.location);
},
[coordinator, props.location],
Expand Down Expand Up @@ -159,23 +157,16 @@ export function PanelDisplay<L extends string>(
aria-label="Tabs"
variant="fullWidth"
>
{panels.map((panel: Panel<L>) => {
{panels.map(({ id, Summary }: Panel) => {
// NOTE: Using this as a tag here is important for React's state system. Otherwise,
// it'll run hooks outside of the normal flow, which breaks things.
const Summary = panel.summary;
return (
<Tab
key={`tab-${panel.id}`}
value={panel.id}
label={<Summary {...props} />}
/>
);
return <Tab key={id} value={id} label={<Summary {...props} />} />;
})}
</Tabs>

<span>
{currentTabName &&
coordinator.listLocations().map((locData: LocationData<L>) => {
coordinator.listLocations().map((locData: LocationData) => {
if (locData.location === props.location) {
return <div key={locData.location} />;
}
Expand Down Expand Up @@ -215,27 +206,26 @@ export function PanelDisplay<L extends string>(
</Toolbar>
</AppBar>

{panels.map((panel: Panel<L>) => {
{panels.map(({ id, Content }: Panel) => {
// NOTE: Using this as a tag here is important for React's state system. Otherwise,
// it'll run hooks outside of the normal flow, which breaks things.
const Content = panel.content;
const height =
(props.dimensions?.height ?? 0 >= 48)
? (props.dimensions?.height ?? 0) - 48
: "auto";

return (
<TabPanel
key={`panel-${panel.id}`}
index={panel.id}
key={id}
index={id}
value={currentTabName}
style={{
overflow: "auto",
height: height || "auto",
position: "relative",
}}
>
{currentTabName === panel.id && <Content {...contentProps} />}
{currentTabName === id && <Content {...contentProps} />}
</TabPanel>
);
})}
Expand Down
Loading
Loading