Skip to content
Open
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
7 changes: 7 additions & 0 deletions .changeset/petrinaut-presentation-profiles.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"@hashintel/petrinaut": patch
---

Add a `presentationProfile` prop to `Petrinaut` (`editor` or `review`) that
gates authoring-only controls, and extract the scenario and playback controls
into shared components reusable outside the full editor.
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ export const EmbeddedExamplePage: FunctionComponent<
handle={handle}
hideNetManagementControls="all"
navigation={navigation}
presentationProfile="review"
readonly
slots={{
topBarStart: (
Expand Down
1 change: 1 addition & 0 deletions apps/petrinaut-website/src/examples/full-example-page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ export const FullExamplePage = ({
handle={handle}
hideNetManagementControls="all"
navigation={navigation}
presentationProfile="review"
readonly
slots={{
topBarStart: (
Expand Down
22 changes: 22 additions & 0 deletions libs/@hashintel/petrinaut/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,28 @@ For host applications that own their Petri net data, implement a
guide lives in the architecture docs:
[Embedding in a host application](https://github.com/hashintel/hash/blob/main/libs/%40local/petrinaut-arch-docs/content/handle/host-integration.mdx).

### Presentation profiles

`presentationProfile` chooses how much editing chrome the component draws:

- `editor`, the default, draws the full authoring surface.
- `review` drops the controls whose only purpose is to change the net: the
add and delete actions in the sidebar lists and property panels. Source
code, custom visualizers, the minimap and the viewport settings stay, so a
reader can still inspect and simulate the model.

The profile is chrome, not enforcement. A host that must not accept edits
passes `readonly` as well, which is what disables the fields themselves:

```tsx
<Petrinaut
handle={handle}
title="My net"
presentationProfile="review"
readonly
/>
```

## Commands and the palette

Petrinaut registers its user-invocable actions (undo, tools, search, panel
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import { lazy, Suspense } from "react";

import type { SubView } from "./types";

type DeferredSubViewOptions = Omit<
SubView,
"component" | "renderHeaderAction"
> & {
load: () => Promise<SubView>;
hasHeaderAction?: boolean;
};

/**
* Keeps an optional subview behind a bundle boundary while preserving the
* synchronous descriptor required by the panel layout.
*/
export const createDeferredSubView = ({
load,
hasHeaderAction = false,
...descriptor
}: DeferredSubViewOptions): SubView => {
const DeferredContent = lazy(async () => {
const subView = await load();
return { default: subView.component };
});
const Content = () => (
<Suspense fallback={null}>
<DeferredContent />
</Suspense>
);

if (!hasHeaderAction) {
return { ...descriptor, component: Content };
}

const DeferredHeaderAction = lazy(async () => {
const subView = await load();
const HeaderAction = () => subView.renderHeaderAction?.() ?? null;
return { default: HeaderAction };
});

return {
...descriptor,
component: Content,
renderHeaderAction: () => (
<Suspense fallback={null}>
<DeferredHeaderAction />
</Suspense>
),
};
};
10 changes: 10 additions & 0 deletions libs/@hashintel/petrinaut/src/ui/components/sub-view/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,16 @@ export interface SubView {
* Only used in vertical (collapsible) layout.
*/
renderHeaderAction?: () => ReactNode;
/**
* Whether the header action creates, deletes or clears something. A
* presentation that hides mutation actions hides only these.
*
* Defaults to false, because the slot also carries close buttons, view
* toggles and status labels, and losing those costs a reader more than a
* disabled Add button does. An action that is a mutation only some of the
* time reads the presentation itself instead of setting this.
*/
headerActionMutates?: boolean;
/**
* Whether this subview should grow to fill available space.
* Only affects vertical layout. Defaults to false.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { css, cva, cx } from "@hashintel/ds-helpers/css";

import { UserSettingsContext } from "../../../../react/state/user-settings-context";
import { useScrollOverflow } from "../../../hooks/use-scroll-overflow";
import { usePetrinautPresentation } from "../../../views/shared/presentation-context";

import type { SubView } from "../types";

Expand Down Expand Up @@ -422,6 +423,7 @@ interface VerticalSubViewsContainerProps {
export const VerticalSubViewsContainer: React.FC<
VerticalSubViewsContainerProps
> = ({ name, subViews, defaultExpanded = true }) => {
const presentation = usePetrinautPresentation();
const { showAnimations, subViewPanels, updateSubViewSection } =
use(UserSettingsContext);

Expand Down Expand Up @@ -500,7 +502,12 @@ export const VerticalSubViewsContainer: React.FC<
renderTitle={subView.renderTitle}
isExpanded={isExpanded}
onToggle={() => toggleSection(subView)}
renderHeaderAction={subView.renderHeaderAction}
renderHeaderAction={
subView.headerActionMutates &&
!presentation.showMutationActions
? undefined
: subView.renderHeaderAction
Comment thread
kube marked this conversation as resolved.
}
Comment thread
kube marked this conversation as resolved.
alwaysShowHeaderAction={subView.alwaysShowHeaderAction}
/>

Expand Down
39 changes: 26 additions & 13 deletions libs/@hashintel/petrinaut/src/ui/petrinaut.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@ import { PetrinautProvider } from "../react/petrinaut-provider";
import { Stack } from "./components/stack";
import { MonacoProvider } from "./monaco/provider";
import { EditorView } from "./views/Editor/editor-view";
import {
PetrinautPresentationProvider,
type PetrinautPresentationProfile,
} from "./views/shared/presentation-context";

// `clip`, not `hidden`: a hidden-overflow box is still programmatically
// scrollable, and focusing an element the canvas transform pushed past the
Expand Down Expand Up @@ -112,6 +116,12 @@ export type PetrinautProps = {
lspWorkerFactory?: LspWorkerFactory;
/** Optional host-controlled, router-neutral app location. */
navigation?: PetrinautNavigationController;
/**
* Presentation policy for the full editor. `review` keeps the full editor
* surface while suppressing authoring actions for route-scoped read-only
* examples. The default remains `editor`.
*/
presentationProfile?: PetrinautPresentationProfile;
Comment thread
kube marked this conversation as resolved.
};

const noop = () => {};
Expand Down Expand Up @@ -140,6 +150,7 @@ export const Petrinaut: FunctionComponent<PetrinautProps> = ({
monteCarloWorkerFactory,
lspWorkerFactory,
navigation,
presentationProfile = "editor",
}) => {
const portalContainerRef = useRef<HTMLDivElement>(null);
const instance = useMemo<Instance>(
Expand Down Expand Up @@ -167,19 +178,21 @@ export const Petrinaut: FunctionComponent<PetrinautProps> = ({
lspWorkerFactory={lspWorkerFactory}
navigation={navigation}
>
<MonacoProvider>
<Stack
className={cx(editorRootStyle, "petrinaut-root")}
ref={portalContainerRef}
>
<EditorView
aiAssistant={aiAssistant}
hideNetManagementControls={hideNetManagementControls}
slots={slots}
viewportActions={viewportActions}
/>
</Stack>
</MonacoProvider>
<PetrinautPresentationProvider profile={presentationProfile}>
<MonacoProvider>
<Stack
className={cx(editorRootStyle, "petrinaut-root")}
ref={portalContainerRef}
>
<EditorView
aiAssistant={aiAssistant}
hideNetManagementControls={hideNetManagementControls}
slots={slots}
viewportActions={viewportActions}
/>
</Stack>
</MonacoProvider>
</PetrinautPresentationProvider>
</PetrinautProvider>
</PortalContainerContext>
);
Expand Down
Loading
Loading