Skip to content
This repository has been archived by the owner on Feb 25, 2024. It is now read-only.

Commit

Permalink
feat(viz): add orientation option to viz
Browse files Browse the repository at this point in the history
  • Loading branch information
judicael_ai committed Apr 14, 2022
1 parent 632f950 commit cfe9a55
Show file tree
Hide file tree
Showing 5 changed files with 157 additions and 36 deletions.
32 changes: 15 additions & 17 deletions src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import { useInterpretCanvas } from './useInterpretCanvas';
import router, { useRouter } from 'next/router';
import { parseEmbedQuery, withoutEmbedQueryParams } from './utils';
import { registryLinks } from './registryLinks';
import { VizOrientationProvider } from './visualizerOrientationContext';

const defaultHeadProps = {
title: 'XState Visualizer',
Expand Down Expand Up @@ -131,23 +132,20 @@ function App({ isEmbedded = false }: { isEmbedded?: boolean }) {
<EditorThemeProvider>
<PaletteProvider value={paletteService}>
<SimulationProvider value={simService}>
<Box
data-testid="app"
data-viz-theme="dark"
as="main"
display="grid"
gridTemplateColumns="1fr auto"
gridTemplateAreas={`"${getGridArea(embed)}"`}
height="100vh"
>
{!(embed?.isEmbedded && embed.mode === EmbedMode.Panels) && (
<CanvasProvider value={canvasService}>
<CanvasView />
</CanvasProvider>
)}
<PanelsView />
<MachineNameChooserModal />
</Box>
<VizOrientationProvider embed={embed}>
<>
{!(
embed?.isEmbedded && embed.mode === EmbedMode.Panels
) && (
<CanvasProvider value={canvasService}>
<CanvasView />
</CanvasProvider>
)}
<PanelsView />

<MachineNameChooserModal />
</>
</VizOrientationProvider>
</SimulationProvider>
</PaletteProvider>
</EditorThemeProvider>
Expand Down
6 changes: 4 additions & 2 deletions src/PanelsView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,12 @@ import { SpinnerWithText } from './SpinnerWithText';
import { StatePanel } from './StatePanel';
import { EmbedMode } from './types';
import { calculatePanelIndexByPanelName } from './utils';
import { useVizOrientation } from './visualizerOrientationContext';

export const PanelsView = (props: BoxProps) => {
const embed = useEmbed();
const simService = useSimulation();
const vizOrientation = useVizOrientation();
const services = useSelector(simService, selectServices);
const [sourceState, sendToSourceService] = useSourceActor();
const [activePanelIndex, setActiveTabIndex] = useState(() =>
Expand All @@ -40,10 +42,10 @@ export const PanelsView = (props: BoxProps) => {
}
}, [embed]);

return (
return vizOrientation.orientation === 'full' ? null : (
<ResizableBox
{...props}
gridArea="panels"
gridArea={vizOrientation.orientation !== 'top/bottom' ? 'panels' : ''}
minHeight={0}
disabled={embed?.isEmbedded && embed.mode !== EmbedMode.Full}
hidden={embed?.isEmbedded && embed.mode === EmbedMode.Viz}
Expand Down
56 changes: 41 additions & 15 deletions src/ResizableBox.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,13 @@ import {
PointDelta,
} from './dragSessionTracker';
import { Point } from './types';
import { useVizOrientation } from './visualizerOrientationContext';

const resizableModel = createModel(
{
ref: null as React.MutableRefObject<HTMLElement> | null,
widthDelta: 0,
heightDelta: 0,
},
{
events: {
Expand Down Expand Up @@ -51,6 +53,9 @@ const resizableMachine = resizableModel.createMachine({
widthDelta: (ctx, e) => {
return Math.max(0, ctx.widthDelta - e.delta.x);
},
heightDelta: (ctx, e) => {
return Math.max(0, ctx.heightDelta - e.delta.y);
},
}),
},
DRAG_SESSION_STOPPED: 'idle',
Expand All @@ -63,6 +68,7 @@ const ResizeHandle: React.FC<{
onChange: (width: number) => void;
}> = ({ onChange }) => {
const ref = useRef<HTMLDivElement>(null!);
const vizOrientation = useVizOrientation();

const [state] = useMachine(
resizableMachine.withContext({
Expand All @@ -72,20 +78,27 @@ const ResizeHandle: React.FC<{
);

useEffect(() => {
onChange(state.context.widthDelta);
}, [state.context.widthDelta, onChange]);
if (vizOrientation.orientation === 'top/bottom') {
onChange(state.context.heightDelta);
} else {
onChange(state.context.widthDelta);
}
}, [state.context.widthDelta, onChange, state.context.heightDelta]);

return (
<Box
ref={ref}
data-testid="resize-handle"
width="1"
width={vizOrientation.orientation === 'top/bottom' ? '100%' : '1px'}
css={{
position: 'absolute',
top: 0,
left: 0,
height: '100%',
cursor: 'ew-resize',
height: vizOrientation.orientation === 'top/bottom' ? '4px' : '100%',
cursor:
vizOrientation.orientation === 'top/bottom'
? 'ns-resize'
: 'ew-resize',
opacity: 0,
transition: 'opacity 0.2s ease',
}}
Expand All @@ -106,7 +119,7 @@ const ResizeHandle: React.FC<{
);
};

interface ResizableBoxProps extends Omit<BoxProps, 'width'> {
interface ResizableBoxProps extends Omit<BoxProps, 'width' | 'height'> {
disabled?: boolean;
}

Expand All @@ -116,20 +129,33 @@ export const ResizableBox: React.FC<ResizableBoxProps> = ({
...props
}) => {
const [widthDelta, setWidthDelta] = useState(0);
const [heightDelta, setHeightDelta] = useState(0);
const vizOrientation = useVizOrientation();
const width =
vizOrientation.orientation === 'top/bottom'
? '100%'
: `clamp(36rem, calc(36rem + ${widthDelta}px), 70vw)`;
const height =
vizOrientation.orientation === 'top/bottom'
? `clamp(36rem, calc(36rem + ${heightDelta}px), 90vh)`
: 'auto';

const handleSizeChange = (value: number) => {
if (vizOrientation.orientation === 'top/bottom') {
setHeightDelta(value);
} else {
setWidthDelta(value);
}
};

return (
// 35rem to avoid shortcut codes breaking
// into multiple lines
<Box
{...props}
style={
!disabled
? { width: `clamp(36rem, calc(36rem + ${widthDelta}px), 70vw)` }
: undefined
}
>
<Box {...props} style={!disabled ? { width, height } : undefined}>
{children}
{!disabled && <ResizeHandle onChange={(value) => setWidthDelta(value)} />}
{!disabled && (
<ResizeHandle onChange={(value) => handleSizeChange(value)} />
)}
</Box>
);
};
26 changes: 24 additions & 2 deletions src/SettingsPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@ import { ThemeName, themes } from './editor-themes';
import { useEditorTheme } from './themeContext';
import { useSimulationMode } from './SimulationContext';
import { getPlatformMetaKeyLabel } from './utils';
import {
OrientationType,
useVizOrientation,
} from './visualizerOrientationContext';

const KeyboardShortcuts = () => (
<Box>
Expand Down Expand Up @@ -58,7 +62,8 @@ const KeyboardShortcuts = () => (
<Td>
<VStack alignItems="flex-start">
<span>
<Kbd>Up</Kbd> , <Kbd>Left</Kbd> , <Kbd>Down</Kbd> , <Kbd>Right</Kbd>
<Kbd>Up</Kbd> , <Kbd>Left</Kbd> , <Kbd>Down</Kbd> ,{' '}
<Kbd>Right</Kbd>
</span>
</VStack>
</Td>
Expand All @@ -68,7 +73,7 @@ const KeyboardShortcuts = () => (
</Tr>
<Tr>
<Td>
<Kbd>Shift</Kbd> + <Kbd>1</Kbd>
<Kbd>Shift</Kbd> + <Kbd>1</Kbd>
</Td>
<Td>Fit to content</Td>
</Tr>
Expand All @@ -90,6 +95,7 @@ const KeyboardShortcuts = () => (
export const SettingsPanel: React.FC = () => {
const editorTheme = useEditorTheme();
const simulationMode = useSimulationMode();
const vizOrientation = useVizOrientation();
return (
<VStack paddingY="5" spacing="7" alignItems="stretch">
{simulationMode === 'visualizing' && <KeyboardShortcuts />}
Expand All @@ -112,6 +118,22 @@ export const SettingsPanel: React.FC = () => {
))}
</Select>
</Box>
<Box>
<Heading as="h2" fontSize="l" marginBottom="5">
Visualizer orientation
</Heading>
<Select
maxWidth="fit-content"
defaultValue={vizOrientation.orientation}
onChange={(e) => {
const orientation = e.target.value as OrientationType;
vizOrientation.changeOrientation(orientation);
}}
>
<option value="left/right">left/right</option>
<option value="top/bottom">top/bottom</option>
</Select>
</Box>
</VStack>
);
};
73 changes: 73 additions & 0 deletions src/visualizerOrientationContext.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import { Box } from '@chakra-ui/react';
import React, { useMemo, useState } from 'react';
import { EmbedContext, EmbedMode } from './types';
import { createRequiredContext } from './utils';

export type OrientationType = 'top/bottom' | 'left/right' | 'full';

const [VizOrientationProviderLocal, useVizOrientation] = createRequiredContext<{
orientation: OrientationType;
changeOrientation(orientation: OrientationType): void;
}>('VisualizerOrientation');

export function VizOrientationProvider({
children,
embed,
}: {
children: React.ReactNode;
embed: EmbedContext;
}) {
const VIZ_LOCAL_KEY = 'xstate_viz_editor_orientation';
const vizLocalOrientation: OrientationType = window.localStorage.getItem(
VIZ_LOCAL_KEY,
) as OrientationType;
const [orientation, setOrientation] = useState<OrientationType>(
vizLocalOrientation ?? 'left/right',
);
const contextValue = useMemo(
() => ({ orientation, changeOrientation: setOrientation }),
[orientation],
);

React.useEffect(() => {
window.localStorage.setItem(VIZ_LOCAL_KEY, orientation);
}, [orientation]);

const getGridArea = (embed?: EmbedContext) => {
if (orientation === 'top/bottom') {
return '';
}

if (embed?.isEmbedded && embed.mode === EmbedMode.Viz) {
return 'canvas';
}

if (embed?.isEmbedded && embed.mode === EmbedMode.Panels) {
return 'panels';
}

return 'canvas panels';
};

return (
<VizOrientationProviderLocal value={contextValue}>
<Box
data-testid="app"
data-viz-theme="dark"
as="main"
display="grid"
gridTemplateColumns={
orientation === 'top/bottom' ? 'repeat(1, 1fr)' : '1fr auto'
}
gridTemplateRows={
orientation === 'top/bottom' ? 'repeat(2, 1fr)' : '1fr auto'
}
gridTemplateAreas={`"${getGridArea(embed)}"`}
height="100vh"
>
{children}
</Box>
</VizOrientationProviderLocal>
);
}
export { useVizOrientation };

0 comments on commit cfe9a55

Please sign in to comment.