Skip to content
This repository was archived by the owner on Feb 25, 2024. It is now read-only.
Open
Show file tree
Hide file tree
Changes from 1 commit
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
41 changes: 39 additions & 2 deletions src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import { MachineNameChooserModal } from './MachineNameChooserModal';
import { PaletteProvider } from './PaletteContext';
import { paletteMachine } from './paletteMachine';
import { PanelsView } from './PanelsView';
import { SimulationProvider } from './SimulationContext';
import { SimulationProvider, useSimulationMode } from './SimulationContext';
import { simulationMachine } from './simulationMachine';
import { getSourceActor, useSourceRegistryData } from './sourceMachine';
import { theme } from './theme';
Expand All @@ -21,6 +21,7 @@ import { useInterpretCanvas } from './useInterpretCanvas';
import router, { useRouter } from 'next/router';
import { parseEmbedQuery, withoutEmbedQueryParams } from './utils';
import { registryLinks } from './registryLinks';
import { canZoom, canZoomIn, canZoomOut } from './canvasMachine';

const defaultHeadProps = {
title: 'XState Visualizer',
Expand Down Expand Up @@ -88,6 +89,7 @@ function App({ isEmbedded = false }: { isEmbedded?: boolean }) {
const paletteService = useInterpret(paletteMachine);
// don't use `devTools: true` here as it would freeze your browser
const simService = useInterpret(simulationMachine);

const machine = useSelector(simService, (state) => {
return state.context.currentSessionId
? state.context.serviceDataMap[state.context.currentSessionId!]?.machine
Expand Down Expand Up @@ -120,6 +122,32 @@ function App({ isEmbedded = false }: { isEmbedded?: boolean }) {
embed,
});

const shouldEnableZoomOutButton = useSelector(
canvasService,
(state) => canZoom(embed) && canZoomOut(state.context),
);

const shouldEnableZoomInButton = useSelector(
canvasService,
(state) => canZoom(embed) && canZoomIn(state.context),
);

const canShowWelcomeMessage = sourceState.hasTag('canShowWelcomeMessage');

const showControls = useMemo(
() => !embed?.isEmbedded || embed.controls,
[embed],
);

const showZoomButtonsInEmbed = useMemo(
() => !embed?.isEmbedded || (embed.controls && embed.zoom),
[embed],
);
const showPanButtonInEmbed = useMemo(
() => !embed?.isEmbedded || (embed.controls && embed.pan),
[embed],
);

// This is because we're doing loads of things on client side anyway
if (!isOnClientSide()) return <VizHead />;

Expand All @@ -142,7 +170,16 @@ function App({ isEmbedded = false }: { isEmbedded?: boolean }) {
>
{!(embed?.isEmbedded && embed.mode === EmbedMode.Panels) && (
<CanvasProvider value={canvasService}>
<CanvasView />
<CanvasView
shouldEnableZoomOutButton={shouldEnableZoomOutButton}
shouldEnableZoomInButton={shouldEnableZoomInButton}
canShowWelcomeMessage={canShowWelcomeMessage}
showControls={showControls}
showZoomButtonsInEmbed={showZoomButtonsInEmbed}
showPanButtonInEmbed={showPanButtonInEmbed}
isEmbedded={embed?.isEmbedded}
hideHeader={false}
/>
</CanvasProvider>
)}
<PanelsView />
Expand Down
86 changes: 34 additions & 52 deletions src/CanvasView.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import {
AddIcon,
MinusIcon,
RepeatIcon,
QuestionOutlineIcon,
RepeatIcon,
} from '@chakra-ui/icons';
import {
Box,
Expand All @@ -19,78 +19,60 @@ import {
VStack,
} from '@chakra-ui/react';
import { useSelector } from '@xstate/react';
import xstatePkgJson from 'xstate/package.json';
import React, { useMemo } from 'react';
import xstatePkgJson from 'xstate/package.json';
import { CanvasContainer } from './CanvasContainer';
import { useCanvas } from './CanvasContext';
import { canZoom, canZoomIn, canZoomOut } from './canvasMachine';
import { CanvasHeader } from './CanvasHeader';
import { toDirectedGraph } from './directedGraph';
import { Graph } from './Graph';
import { useSimulation, useSimulationMode } from './SimulationContext';
import { CanvasHeader } from './CanvasHeader';
import { Overlay } from './Overlay';
import { useEmbed } from './embedContext';
import { CompressIcon, HandIcon } from './Icons';
import { useSourceActor } from './sourceMachine';
import { Overlay } from './Overlay';
import { useSimulation, useSimulationMode } from './SimulationContext';
import { WelcomeArea } from './WelcomeArea';

export const CanvasView: React.FC = () => {
export const CanvasView = (props: {
shouldEnableZoomOutButton?: boolean;
shouldEnableZoomInButton?: boolean;
canShowWelcomeMessage?: boolean;
showControls?: boolean;
showZoomButtonsInEmbed?: boolean;
showPanButtonInEmbed?: boolean;
isEmbedded?: boolean;
hideHeader: boolean;
}) => {
// TODO: refactor this so an event can be explicitly sent to a machine
// it isn't straightforward to do at the moment cause the target machine lives in a child component
const [panModeEnabled, setPanModeEnabled] = React.useState(false);
const embed = useEmbed();
const simService = useSimulation();
const canvasService = useCanvas();
const [sourceState] = useSourceActor();

const simService = useSimulation();

const machine = useSelector(simService, (state) => {
return state.context.currentSessionId
? state.context.serviceDataMap[state.context.currentSessionId!]?.machine
: undefined;
});
const isLayoutPending = useSelector(simService, (state) =>
state.hasTag('layoutPending'),
);
const isEmpty = useSelector(simService, (state) => state.hasTag('empty'));
const digraph = useMemo(
() => (machine ? toDirectedGraph(machine) : undefined),
[machine],
);

const shouldEnableZoomOutButton = useSelector(
canvasService,
(state) => canZoom(embed) && canZoomOut(state.context),
);

const shouldEnableZoomInButton = useSelector(
canvasService,
(state) => canZoom(embed) && canZoomIn(state.context),
);

const simulationMode = useSimulationMode();

const canShowWelcomeMessage = sourceState.hasTag('canShowWelcomeMessage');

const showControls = useMemo(
() => !embed?.isEmbedded || embed.controls,
[embed],
const digraph = useMemo(
() => (machine ? toDirectedGraph(machine) : undefined),
[machine],
);

const showZoomButtonsInEmbed = useMemo(
() => !embed?.isEmbedded || (embed.controls && embed.zoom),
[embed],
);
const showPanButtonInEmbed = useMemo(
() => !embed?.isEmbedded || (embed.controls && embed.pan),
[embed],
const isLayoutPending = useSelector(simService, (state) =>
state.hasTag('layoutPending'),
);
const isEmpty = useSelector(simService, (state) => state.hasTag('empty'));

return (
<Box
display="grid"
height="100%"
{...(!embed?.isEmbedded && { gridTemplateRows: '3rem 1fr auto' })}
{...(!props.hideHeader && { gridTemplateRows: '3rem 1fr auto' })}
>
{!embed?.isEmbedded && (
{!props.hideHeader && (
<Box data-testid="canvas-header" bg="gray.800" zIndex={1} padding="0">
<CanvasHeader />
</Box>
Expand All @@ -107,10 +89,10 @@ export const CanvasView: React.FC = () => {
</Box>
</Overlay>
)}
{isEmpty && canShowWelcomeMessage && <WelcomeArea />}
{isEmpty && props.canShowWelcomeMessage && <WelcomeArea />}
</CanvasContainer>

{showControls && (
{props.showControls && (
<Box
display="flex"
flexDirection="row"
Expand All @@ -126,21 +108,21 @@ export const CanvasView: React.FC = () => {
data-testid="controls"
>
<ButtonGroup size="sm" spacing={2} isAttached>
{showZoomButtonsInEmbed && (
{props.showZoomButtonsInEmbed && (
<>
<IconButton
aria-label="Zoom out"
title="Zoom out"
icon={<MinusIcon />}
disabled={!shouldEnableZoomOutButton}
disabled={!props.shouldEnableZoomOutButton}
onClick={() => canvasService.send('ZOOM.OUT')}
variant="secondary"
/>
<IconButton
aria-label="Zoom in"
title="Zoom in"
icon={<AddIcon />}
disabled={!shouldEnableZoomInButton}
disabled={!props.shouldEnableZoomInButton}
onClick={() => canvasService.send('ZOOM.IN')}
variant="secondary"
/>
Expand All @@ -153,7 +135,7 @@ export const CanvasView: React.FC = () => {
onClick={() => canvasService.send('FIT_TO_CONTENT')}
variant="secondary"
/>
{!embed?.isEmbedded && (
{!props.isEmbedded && (
<IconButton
aria-label="Reset canvas"
title="Reset canvas"
Expand All @@ -163,7 +145,7 @@ export const CanvasView: React.FC = () => {
/>
)}
</ButtonGroup>
{showPanButtonInEmbed && (
{props.showPanButtonInEmbed && (
<IconButton
aria-label="Pan mode"
icon={<HandIcon />}
Expand All @@ -184,7 +166,7 @@ export const CanvasView: React.FC = () => {
RESET
</Button>
)}
{!embed?.isEmbedded && (
{!props.isEmbedded && (
<Menu closeOnSelect={true} placement="top-end">
<MenuButton
as={IconButton}
Expand Down
10 changes: 6 additions & 4 deletions src/embedContext.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { createContext, useContext } from 'react';
import { EmbedContext } from './types';
import { createRequiredContext } from './utils';

export const [EmbedProvider, useEmbed] = createRequiredContext<
EmbedContext | undefined
>('Embed');
const EmbedReactContext = createContext(null as EmbedContext);

export const EmbedProvider = EmbedReactContext.Provider;

export const useEmbed = () => useContext(EmbedReactContext);
15 changes: 14 additions & 1 deletion src/pages/_app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import '../InvokeViz.scss';
import '../monacoPatch';
import '../StateNodeViz.scss';
import '../TransitionViz.scss';
import { NextComponentWithMeta } from '../types';

// import { isOnClientSide } from '../isOnClientSide';

Expand All @@ -32,7 +33,7 @@ if (
});
}

const MyApp = ({ pageProps, Component }: AppProps) => {
const AuthWrapper = ({ pageProps, Component }: AppProps) => {
const router = useRouter();

const authService = useInterpret(
Expand All @@ -50,4 +51,16 @@ const MyApp = ({ pageProps, Component }: AppProps) => {
);
};

const MyApp = (
props: AppProps & {
Component: NextComponentWithMeta;
},
) => {
if (props.Component.preventAuth) {
return <props.Component {...props.pageProps}></props.Component>;
}

return <AuthWrapper {...props}></AuthWrapper>;
};

export default MyApp;
91 changes: 91 additions & 0 deletions src/pages/view-only.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
import { Box, ChakraProvider } from '@chakra-ui/react';
import { useInterpret } from '@xstate/react';
import Head from 'next/head';
import { useRouter } from 'next/router';
import React, { useEffect, useMemo } from 'react';
import { createMachine } from 'xstate';
import { CanvasProvider } from '../CanvasContext';
import { CanvasView } from '../CanvasView';
import { EmbedProvider } from '../embedContext';
import { SimulationProvider } from '../SimulationContext';
import { simulationMachine } from '../simulationMachine';
import { theme } from '../theme';
import { NextComponentWithMeta } from '../types';
import { useInterpretCanvas } from '../useInterpretCanvas';
import { parseEmbedQuery, withoutEmbedQueryParams } from '../utils';

const machine = createMachine({
initial: 'wow',
states: {
wow: {
on: {
NEXT: {
target: 'new',
},
},
},
new: {},
},
});

const ViewOnlyPage: NextComponentWithMeta = () => {
const canvasService = useInterpretCanvas({
sourceID: null,
});
const simulationService = useInterpret(simulationMachine);
const router = useRouter();

useEffect(() => {
simulationService.send({
type: 'MACHINES.REGISTER',
machines: [machine],
});
}, []);

const embed = useMemo(
() => ({
...parseEmbedQuery(router.query),
isEmbedded: true,
originalUrl: withoutEmbedQueryParams(router.query),
}),
[router.query],
);
return (
<>
<Head>
<script src="https://unpkg.com/elkjs@0.7.1/lib/elk.bundled.js"></script>
</Head>
<EmbedProvider value={embed}>
<ChakraProvider theme={theme}>
<CanvasProvider value={canvasService}>
<SimulationProvider value={simulationService}>
<Box
data-testid="app"
data-viz-theme="dark"
as="main"
display="grid"
gridTemplateColumns="1fr auto"
gridTemplateAreas={`"canvas"`}
height="100vh"
>
<CanvasView
hideHeader
showControls
isEmbedded
shouldEnableZoomInButton
shouldEnableZoomOutButton
showPanButtonInEmbed
showZoomButtonsInEmbed
/>
</Box>
</SimulationProvider>
</CanvasProvider>
</ChakraProvider>
</EmbedProvider>
</>
);
};

ViewOnlyPage.preventAuth = true;

export default ViewOnlyPage;
Loading