diff --git a/.changeset/config.json b/.changeset/config.json index a5694e506e9..24c91b31c43 100644 --- a/.changeset/config.json +++ b/.changeset/config.json @@ -11,7 +11,6 @@ "@local/*", "@rust/*", "@tests/*", - "@hashintel/petrinaut-old", "@hashintel/block-design-system", "@hashintel/design-system", "@hashintel/query-editor", diff --git a/apps/hash-frontend/next.config.js b/apps/hash-frontend/next.config.js index 834cdfa4084..606f4cd6a5f 100644 --- a/apps/hash-frontend/next.config.js +++ b/apps/hash-frontend/next.config.js @@ -158,7 +158,9 @@ export default withSentryConfig( "@emotion/server", "@hashintel/block-design-system", "@hashintel/design-system", - "@hashintel/petrinaut-old", + "@hashintel/petrinaut", + "@hashintel/ds-components", + "@hashintel/ds-helpers", "@hashintel/type-editor", "echarts", "zrender", @@ -217,6 +219,20 @@ export default withSentryConfig( // eslint-disable-next-line no-param-reassign webpackConfig.resolve.alias.canvas = false; + if (!isServer) { + // Stub Node.js built-ins for browser — needed by `typescript` (used by + // @hashintel/petrinaut's in-browser language service) + // eslint-disable-next-line no-param-reassign + webpackConfig.resolve.fallback = { + ...webpackConfig.resolve.fallback, + module: false, + fs: false, + path: false, + os: false, + perf_hooks: false, + }; + } + webpackConfig.plugins.push( new DefinePlugin({ __SENTRY_DEBUG__: false, diff --git a/apps/hash-frontend/package.json b/apps/hash-frontend/package.json index 2e1d13af8ec..3beed7896ed 100644 --- a/apps/hash-frontend/package.json +++ b/apps/hash-frontend/package.json @@ -36,7 +36,9 @@ "@glideapps/glide-data-grid": "patch:@glideapps/glide-data-grid@npm%3A6.0.3#~/.yarn/patches/@glideapps-glide-data-grid-npm-6.0.3-f71d586425.patch", "@hashintel/block-design-system": "workspace:*", "@hashintel/design-system": "workspace:*", - "@hashintel/petrinaut-old": "workspace:*", + "@hashintel/ds-components": "workspace:*", + "@hashintel/ds-helpers": "workspace:*", + "@hashintel/petrinaut": "workspace:*", "@hashintel/query-editor": "workspace:*", "@hashintel/type-editor": "workspace:*", "@lit-labs/react": "1.2.1", diff --git a/apps/hash-frontend/src/lib/csp.ts b/apps/hash-frontend/src/lib/csp.ts index 4b327029a49..171adfb0a5b 100644 --- a/apps/hash-frontend/src/lib/csp.ts +++ b/apps/hash-frontend/src/lib/csp.ts @@ -24,6 +24,9 @@ export const buildCspHeader = (nonce: string): string => { "https://apis.google.com", // Vercel toolbar / live preview widget "https://vercel.live", + // @todo FE-488 will make this unnecessary + // Monaco Editor loaded from CDN by @monaco-editor/react (used by petrinaut) + "https://cdn.jsdelivr.net", ], "style-src": [ @@ -31,6 +34,9 @@ export const buildCspHeader = (nonce: string): string => { // Required for Emotion/MUI CSS-in-JS inline style injection. // @todo Use nonce-based approach via Emotion's cache `nonce` option. "'unsafe-inline'", + // @todo FE-488 will make this unnecessary + // Monaco Editor stylesheet loaded from CDN by @monaco-editor/react (used by petrinaut) + "https://cdn.jsdelivr.net", ], "img-src": [ @@ -45,7 +51,12 @@ export const buildCspHeader = (nonce: string): string => { ...(process.env.NODE_ENV === "development" ? ["http:"] : []), ], - "font-src": ["'self'"], + "font-src": [ + "'self'", + // @todo FE-488 will make this unnecessary + // Monaco Editor CSS embeds the Codicon icon font as an inline base64 data URI + "data:", + ], "connect-src": [ "'self'", diff --git a/apps/hash-frontend/src/pages/process.page.tsx b/apps/hash-frontend/src/pages/process.page.tsx index 3828cee0334..610766a97a7 100644 --- a/apps/hash-frontend/src/pages/process.page.tsx +++ b/apps/hash-frontend/src/pages/process.page.tsx @@ -1,6 +1,17 @@ +import dynamic from "next/dynamic"; + import type { NextPageWithLayout } from "../shared/layout"; import { getLayoutWithSidebar } from "../shared/layout"; -import { ProcessEditorWrapper } from "./process.page/process-editor-wrapper"; + +// Petrinaut uses Web Workers, Canvas, Monaco Editor, and the TypeScript compiler +// which all require browser APIs — must not be server-rendered. +const ProcessEditorWrapper = dynamic( + () => + import("./process.page/process-editor-wrapper").then((mod) => ({ + default: mod.ProcessEditorWrapper, + })), + { ssr: false }, +); const ProcessPage: NextPageWithLayout = () => { return ; diff --git a/apps/hash-frontend/src/pages/process.page/process-editor-wrapper.tsx b/apps/hash-frontend/src/pages/process.page/process-editor-wrapper.tsx index 9cba60ef30c..dadd791673d 100644 --- a/apps/hash-frontend/src/pages/process.page/process-editor-wrapper.tsx +++ b/apps/hash-frontend/src/pages/process.page/process-editor-wrapper.tsx @@ -1,7 +1,9 @@ +import "@hashintel/petrinaut/dist/main.css"; + import type { EntityId } from "@blockprotocol/type-system"; import { AlertModal } from "@hashintel/design-system"; -import type { PetriNetDefinitionObject } from "@hashintel/petrinaut-old"; -import { defaultTokenTypes, Petrinaut } from "@hashintel/petrinaut-old"; +import type { SDCPN } from "@hashintel/petrinaut"; +import { Petrinaut } from "@hashintel/petrinaut"; import { Box, Stack } from "@mui/material"; import { produce } from "immer"; import { useCallback, useMemo, useState } from "react"; @@ -15,22 +17,17 @@ import { export const ProcessEditorWrapper = () => { const [selectedNetId, setSelectedNetId] = useState(null); const [title, setTitle] = useState("Process"); - const [parentNet, setParentNet] = useState<{ - parentNetId: EntityId; - title: string; - } | null>(null); - - const [petriNetDefinition, setPetriNetDefinition] = - useState({ - arcs: [], - nodes: [], - tokenTypes: defaultTokenTypes, - }); + + const [petriNetDefinition, setPetriNetDefinition] = useState({ + places: [], + transitions: [], + types: [], + differentialEquations: [], + parameters: [], + }); const mutatePetriNetDefinition = useCallback( - ( - mutationFn: (petriNetDefinition: PetriNetDefinitionObject) => undefined, - ) => { + (mutationFn: (petriNetDefinition: SDCPN) => void) => { setPetriNetDefinition((netDefinition) => { const updatedNetDefinition = produce(netDefinition, (draft) => { mutationFn(draft); @@ -55,10 +52,8 @@ export const ProcessEditorWrapper = () => { userEditable, setUserEditable, } = useProcessSaveAndLoad({ - parentNet, petriNet: petriNetDefinition, selectedNetId, - setParentNet, setPetriNet: setPetriNetDefinition, setSelectedNetId, setTitle, @@ -70,17 +65,16 @@ export const ProcessEditorWrapper = () => { petriNetDefinition: newPetriNetDefinition, title: newTitle, }: { - petriNetDefinition: PetriNetDefinitionObject; + petriNetDefinition: SDCPN; title: string; }) => { setPetriNetDefinition(newPetriNetDefinition); setSelectedNetId(null); - setParentNet(null); setUserEditable(true); setTitle(newTitle); }, - [setParentNet, setSelectedNetId, setUserEditable, setTitle], + [setSelectedNetId, setUserEditable, setTitle], ); const loadNetFromId = useCallback( @@ -100,7 +94,7 @@ export const ProcessEditorWrapper = () => { [isDirty, loadPersistedNet, persistedNets], ); - const childProcessOptions = useMemo(() => { + const existingNetOptions = useMemo(() => { return persistedNets .filter((net) => net.userEditable && net.entityId !== selectedNetId) .map((net) => ({ @@ -140,13 +134,13 @@ export const ProcessEditorWrapper = () => { loadNetFromId(id as EntityId)} - parentNet={parentNet} petriNetDefinition={petriNetDefinition} petriNetId={selectedNetId} mutatePetriNetDefinition={mutatePetriNetDefinition} + readonly={!userEditable} setTitle={setTitle} title={title} /> diff --git a/apps/hash-frontend/src/pages/process.page/process-editor-wrapper/use-process-save-and-load.tsx b/apps/hash-frontend/src/pages/process.page/process-editor-wrapper/use-process-save-and-load.tsx index 9534cc36e16..1ab41904447 100644 --- a/apps/hash-frontend/src/pages/process.page/process-editor-wrapper/use-process-save-and-load.tsx +++ b/apps/hash-frontend/src/pages/process.page/process-editor-wrapper/use-process-save-and-load.tsx @@ -2,23 +2,16 @@ import { useMutation } from "@apollo/client"; import type { EntityId, PropertyObjectWithMetadata, - PropertyPatchOperation, } from "@blockprotocol/type-system"; -import type { - PetriNetDefinitionObject, - TransitionNodeType, -} from "@hashintel/petrinaut-old"; +import type { SDCPN } from "@hashintel/petrinaut"; +import { isSDCPNEqual } from "@hashintel/petrinaut"; import { HashEntity } from "@local/hash-graph-sdk/entity"; import { blockProtocolDataTypes, systemEntityTypes, - systemLinkEntityTypes, systemPropertyTypes, } from "@local/hash-isomorphic-utils/ontology-type-ids"; -import type { - PetriNetPropertiesWithMetadata, - SubProcessOfPropertiesWithMetadata, -} from "@local/hash-isomorphic-utils/system-types/petrinet"; +import type { PetriNetPropertiesWithMetadata } from "@local/hash-isomorphic-utils/system-types/petrinet"; import { type Dispatch, type SetStateAction, @@ -28,66 +21,40 @@ import { } from "react"; import type { - ArchiveEntityMutation, - ArchiveEntityMutationVariables, CreateEntityMutation, CreateEntityMutationVariables, UpdateEntityMutation, UpdateEntityMutationVariables, } from "../../../graphql/api-types.gen"; import { - archiveEntityMutation, createEntityMutation, updateEntityMutation, } from "../../../graphql/queries/knowledge/entity.queries"; import { useActiveWorkspace } from "../../shared/workspace-context"; -import { updateSubProcessDefinitionForParentPlaces } from "./use-process-save-and-load/update-sub-process-nodes"; import { getPersistedNetsFromSubgraph, usePersistedNets, } from "./use-process-save-and-load/use-persisted-nets"; -const areSetsEquivalent = (a: Set, b: Set) => { - if (a.size !== b.size) { - return false; - } - - return a.isSubsetOf(b); -}; - export type PersistedNet = { entityId: EntityId; title: string; - definition: PetriNetDefinitionObject; - parentNet: { parentNetId: EntityId; title: string } | null; - childNetLinksByNodeIdAndChildNetId: { - [nodeId: string]: { - [childNetId: string]: { - linkEntityId: EntityId; - }; - }; - }; + definition: SDCPN; userEditable: boolean; }; type UseProcessSaveAndLoadParams = { - parentNet: { parentNetId: EntityId; title: string } | null; - petriNet: PetriNetDefinitionObject; + petriNet: SDCPN; selectedNetId: EntityId | null; - setParentNet: Dispatch< - SetStateAction<{ parentNetId: EntityId; title: string } | null> - >; - setPetriNet: Dispatch>; + setPetriNet: Dispatch>; setSelectedNetId: Dispatch>; setTitle: Dispatch>; title: string; }; export const useProcessSaveAndLoad = ({ - parentNet, petriNet, selectedNetId, - setParentNet, setSelectedNetId, setPetriNet, setTitle, @@ -120,11 +87,6 @@ export const useProcessSaveAndLoad = ({ UpdateEntityMutationVariables >(updateEntityMutation); - const [archiveEntity] = useMutation< - ArchiveEntityMutation, - ArchiveEntityMutationVariables - >(archiveEntityMutation); - const persistedNet = useMemo(() => { return persistedNets.find((net) => net.entityId === selectedNetId); }, [persistedNets, selectedNetId]); @@ -134,61 +96,20 @@ export const useProcessSaveAndLoad = ({ return true; } - if (title !== persistedNet.title) { - return true; - } - - if (parentNet?.parentNetId !== persistedNet.parentNet?.parentNetId) { - return true; - } - - if (petriNet.arcs.length !== persistedNet.definition.arcs.length) { - return true; - } - - if (petriNet.nodes.length !== persistedNet.definition.nodes.length) { - return true; - } - - if ( - petriNet.tokenTypes.length !== persistedNet.definition.tokenTypes.length - ) { - return true; - } - - if ( - JSON.stringify(petriNet.arcs.map(({ selected: _, ...arc }) => arc)) !== - JSON.stringify(persistedNet.definition.arcs) - ) { - return true; - } - - if ( - JSON.stringify(petriNet.nodes) !== - JSON.stringify(persistedNet.definition.nodes) - ) { - return true; - } - - if ( - JSON.stringify(petriNet.tokenTypes) !== - JSON.stringify(persistedNet.definition.tokenTypes) - ) { - return true; - } - - return false; - }, [petriNet, persistedNet, title, parentNet]); + return ( + title !== persistedNet.title || + !isSDCPNEqual(petriNet, persistedNet.definition) + ); + }, [petriNet, persistedNet, title]); const loadPersistedNet = useCallback( (net: PersistedNet) => { setSelectedNetId(net.entityId); - setParentNet(net.parentNet); setPetriNet(net.definition); setTitle(net.title); setUserEditable(net.userEditable); }, - [setParentNet, setPetriNet, setSelectedNetId, setTitle, setUserEditable], + [setPetriNet, setSelectedNetId, setTitle, setUserEditable], ); const refetchPersistedNets = useCallback( @@ -236,13 +157,7 @@ export const useProcessSaveAndLoad = ({ metadata: { dataTypeId: blockProtocolDataTypes.object.dataTypeId, }, - // @ts-expect-error -- incompatibility between JsonValue and some of the Edge types - // @todo fix this - value: { - arcs: petriNet.arcs, - nodes: petriNet.nodes, - tokenTypes: petriNet.tokenTypes, - } satisfies PetriNetDefinitionObject, + value: petriNet, }, }, { @@ -270,11 +185,7 @@ export const useProcessSaveAndLoad = ({ metadata: { dataTypeId: blockProtocolDataTypes.object.dataTypeId, }, - value: { - arcs: petriNet.arcs, - nodes: petriNet.nodes, - tokenTypes: petriNet.tokenTypes, - } satisfies PetriNetDefinitionObject, + value: petriNet, }, "https://hash.ai/@h/types/property-type/title/": { metadata: { @@ -300,297 +211,6 @@ export const useProcessSaveAndLoad = ({ throw new Error("Somehow no entityId available after persisting net"); } - /** - * If we have to make changes to places in a linked sub-process, we will need this. - * and it's cheaper to build it once here rather than finding labels repeatedly as and when we need them. - * There shouldn't be many places, so it doesn't really matter if we don't end up needing it. - */ - const placeLabelsById: Record = {}; - for (const node of petriNet.nodes) { - if (node.data.type === "place") { - placeLabelsById[node.id] = node.data.label; - } - } - - /** - * Handle sub-process changes. For any sub-process changed on a transition: - * 1. Archive the previous sub-process, if there was one - * 2. Create a new link to the new sub-process, if there is one - */ - for (const node of petriNet.nodes) { - if (node.data.type !== "transition") { - continue; - } - - const previousTransition = persistedNet?.definition.nodes.find( - (transition): transition is TransitionNodeType => - transition.data.type === "transition" && transition.id === node.id, - ); - - const previousChildNetReference = previousTransition?.data.childNet; - - const oldChildNetId = previousChildNetReference?.childNetId; - const newChildNetId = node.data.childNet?.childNetId; - - const childNetIdentityHasChanged = oldChildNetId !== newChildNetId; - - const existingLinkEntityId = oldChildNetId - ? persistedNet?.childNetLinksByNodeIdAndChildNetId[node.id]?.[ - oldChildNetId - ]?.linkEntityId - : null; - - /** - * Archive the link to the previous sub-process, if it has changed. - */ - if ( - childNetIdentityHasChanged && - existingLinkEntityId && - previousChildNetReference - ) { - await archiveEntity({ - variables: { entityId: existingLinkEntityId }, - }); - - const previousChildNet = persistedNets.find( - (net) => net.entityId === previousChildNetReference.childNetId, - ); - - if (!previousChildNet) { - throw new Error( - `Sub-process ${previousChildNetReference.childNetId} not found`, - ); - } - - /** - * Get the new nodes for the sub-process, with any links to parent places removed. - */ - const updatedChildNetNodes = updateSubProcessDefinitionForParentPlaces({ - subProcessNet: previousChildNet, - inputPlaceLabelById: {}, - outputPlaceLabelById: {}, - }); - - if (updatedChildNetNodes) { - await updateEntity({ - variables: { - entityUpdate: { - entityId: previousChildNet.entityId, - propertyPatches: [ - { - op: "replace", - path: [ - systemPropertyTypes.definitionObject.propertyTypeBaseUrl, - ], - property: { - // @ts-expect-error -- incompatibility between JsonValue and some of the Edge types - // @todo fix this - value: { - ...previousChildNet.definition, - nodes: updatedChildNetNodes, - } satisfies PetriNetDefinitionObject, - metadata: { - dataTypeId: blockProtocolDataTypes.object.dataTypeId, - }, - }, - }, - ], - }, - }, - }); - } - } - - const childNetDefinition = persistedNets.find( - (net) => net.entityId === newChildNetId, - ); - - if (newChildNetId && !childNetDefinition) { - throw new Error(`Sub-process ${newChildNetId} not found`); - } - - /** - * Create the link from the sub-process to the parent process, if it has changed. - */ - if (childNetIdentityHasChanged && newChildNetId && node.data.childNet) { - await createEntity({ - variables: { - entityTypeIds: [ - systemLinkEntityTypes.subProcessOf.linkEntityTypeId, - ], - linkData: { - leftEntityId: newChildNetId as EntityId, - rightEntityId: persistedEntityId, - }, - properties: { - value: { - "https://hash.ai/@h/types/property-type/transition-id/": { - metadata: { - dataTypeId: blockProtocolDataTypes.text.dataTypeId, - }, - value: node.id, - }, - "https://hash.ai/@h/types/property-type/input-place-id/": { - value: node.data.childNet.inputPlaceIds.map((id) => ({ - metadata: { - dataTypeId: blockProtocolDataTypes.text.dataTypeId, - }, - value: id, - })), - }, - "https://hash.ai/@h/types/property-type/output-place-id/": { - value: node.data.childNet.outputPlaceIds.map((id) => ({ - metadata: { - dataTypeId: blockProtocolDataTypes.text.dataTypeId, - }, - value: id, - })), - }, - }, - } satisfies SubProcessOfPropertiesWithMetadata as PropertyObjectWithMetadata, - webId: activeWorkspaceWebId, - }, - }); - } - - if (!node.data.childNet || !childNetDefinition) { - /** - * If there is no linked sub-process now, we are done for this transition node. - */ - continue; - } - - /** - * If there is a linked sub-process, we need to check if the selected input or output places for the parent transition node have changed, - * so that we can make sure they are represented in the sub-process. - */ - const childNetInputPlaceIdsChanged = !areSetsEquivalent( - new Set(node.data.childNet.inputPlaceIds), - new Set(previousTransition?.data.childNet?.inputPlaceIds ?? []), - ); - - const childNetOutputPlaceIdsChanged = !areSetsEquivalent( - new Set(node.data.childNet.outputPlaceIds), - new Set( - previousTransition?.data.childNet?.outputPlaceIds ?? [], - ), - ); - - if (childNetInputPlaceIdsChanged || childNetOutputPlaceIdsChanged) { - if (existingLinkEntityId) { - /** - * If we already have a link, we need to update the existing link entity to represent the new input and output places. - */ - const propertyPatches: PropertyPatchOperation[] = []; - - if (childNetInputPlaceIdsChanged) { - propertyPatches.push({ - op: "replace", - path: [systemPropertyTypes.inputPlaceId.propertyTypeBaseUrl], - property: { - value: node.data.childNet.inputPlaceIds.map((id) => ({ - metadata: { - dataTypeId: blockProtocolDataTypes.text.dataTypeId, - }, - value: id, - })), - }, - }); - } - - if (childNetOutputPlaceIdsChanged) { - propertyPatches.push({ - op: "replace", - path: [systemPropertyTypes.outputPlaceId.propertyTypeBaseUrl], - property: { - value: node.data.childNet.outputPlaceIds.map((id) => ({ - metadata: { - dataTypeId: blockProtocolDataTypes.text.dataTypeId, - }, - value: id, - })), - }, - }); - } - - await updateEntity({ - variables: { - entityUpdate: { - entityId: existingLinkEntityId, - propertyPatches, - }, - }, - }); - } - - /** - * Now we need to ensure that the input and output places in the parent process are represented in the sub-process. - */ - const inputPlaceLabelById: Record = {}; - const outputPlaceLabelById: Record = {}; - - for (const placeId of node.data.childNet.inputPlaceIds) { - const label = placeLabelsById[placeId]; - - if (!label) { - throw new Error(`Place ${placeId} not found when looking up label`); - } - - inputPlaceLabelById[placeId] = label; - } - - for (const placeId of node.data.childNet.outputPlaceIds) { - const label = placeLabelsById[placeId]; - - if (!label) { - throw new Error(`Place ${placeId} not found when looking up label`); - } - - outputPlaceLabelById[placeId] = label; - } - - const newNodes = updateSubProcessDefinitionForParentPlaces({ - subProcessNet: childNetDefinition, - inputPlaceLabelById, - outputPlaceLabelById, - }); - - if (!newNodes) { - /** - * No changes to the sub-process necessary. - */ - continue; - } - - await updateEntity({ - variables: { - entityUpdate: { - entityId: childNetDefinition.entityId, - propertyPatches: [ - { - op: "replace", - path: [ - systemPropertyTypes.definitionObject.propertyTypeBaseUrl, - ], - property: { - // @ts-expect-error -- incompatibility between JsonValue and some of the Edge types - // @todo fix this - value: { - ...childNetDefinition.definition, - nodes: newNodes, - } satisfies PetriNetDefinitionObject, - metadata: { - dataTypeId: blockProtocolDataTypes.object.dataTypeId, - }, - }, - }, - ], - }, - }, - }); - } - } - await refetchPersistedNets({ updatedEntityId: persistedEntityId }); setSelectedNetId(persistedEntityId); setUserEditable(true); @@ -598,14 +218,8 @@ export const useProcessSaveAndLoad = ({ setPersistPending(false); }, [ activeWorkspaceWebId, - archiveEntity, createEntity, - persistedNet?.definition.nodes, - persistedNet?.childNetLinksByNodeIdAndChildNetId, - persistedNets, - petriNet.arcs, - petriNet.nodes, - petriNet.tokenTypes, + petriNet, refetchPersistedNets, selectedNetId, setSelectedNetId, diff --git a/apps/hash-frontend/src/pages/process.page/process-editor-wrapper/use-process-save-and-load/update-sub-process-nodes.ts b/apps/hash-frontend/src/pages/process.page/process-editor-wrapper/use-process-save-and-load/update-sub-process-nodes.ts deleted file mode 100644 index 83abe732bd2..00000000000 --- a/apps/hash-frontend/src/pages/process.page/process-editor-wrapper/use-process-save-and-load/update-sub-process-nodes.ts +++ /dev/null @@ -1,189 +0,0 @@ -import type { NodeType } from "@hashintel/petrinaut-old"; -import { nodeDimensions } from "@hashintel/petrinaut-old"; -import { generateUuid } from "@local/hash-isomorphic-utils/generate-uuid"; - -import type { PersistedNet } from "../use-process-save-and-load"; - -/** - * Updates the input and output place nodes in a sub-process net to include new input and output places from the parent transition, - * and remove links to input and output places in the parent which are no longer selected as relevant to the sub-process. - * - * New input places are positioned above the leftmost node(s), and new output places above the rightmost node(s). - * - * @param subProcessNet - The sub-process net to update - * @param inputPlaceLabelById - A map of relevant input place IDs to their labels - * @param outputPlaceLabelById - A map of relevant output place IDs to their labels - * - * @returns The new sub-process nodes, or null if no changes were made - */ -export const updateSubProcessDefinitionForParentPlaces = ({ - subProcessNet, - inputPlaceLabelById, - outputPlaceLabelById, -}: { - subProcessNet: PersistedNet; - inputPlaceLabelById: Record; - outputPlaceLabelById: Record; -}): NodeType[] | null => { - const { nodes } = subProcessNet.definition; - - /** - * We want to find the leftmost and rightmost places, and the minimum y-coordinates for the leftmost and rightmost places, - * so that we can position the new input and output places above the leftmost and rightmost places, respectively. - */ - let minX = Number.POSITIVE_INFINITY; - let maxX = Number.NEGATIVE_INFINITY; - let minYLeft = Number.POSITIVE_INFINITY; - let minYRight = Number.POSITIVE_INFINITY; - - const newNodes: NodeType[] = []; - - const inputPlaceIds = Object.keys(inputPlaceLabelById); - const outputPlaceIds = Object.keys(outputPlaceLabelById); - - const inputPlaceIdsToAdd = new Set(inputPlaceIds); - const outputPlaceIdsToAdd = new Set(outputPlaceIds); - - let nodesUnlinkedCount = 0; - - for (const node of nodes) { - /** - * Register the node's position so that we can position any new input and output places correctly later. - */ - const x = node.position.x; - const y = node.position.y; - - if (x < minX) { - minX = x; - minYLeft = y; - } else if (x === minX) { - minYLeft = Math.min(minYLeft, y); - } - - if (x > maxX) { - maxX = x; - minYRight = y; - } else if (x === maxX) { - minYRight = Math.min(minYRight, y); - } - - if ( - node.data.type === "place" && - node.data.parentNetNode && - ((node.data.parentNetNode.type === "input" && - !inputPlaceIds.includes(node.data.parentNetNode.id)) || - (node.data.parentNetNode.type === "output" && - !outputPlaceIds.includes(node.data.parentNetNode.id))) - ) { - /** - * This is an input or output place node from the parent which is no longer relevant to the sub-process, - * so we remove the link to the parent process node. - */ - const { parentNetNode: _, ...restData } = node.data; - - newNodes.push({ - ...node, - data: restData, - }); - - nodesUnlinkedCount++; - - continue; - } - - if (node.data.type === "place" && node.data.parentNetNode) { - /** - * Register the fact that we've seen a linked input or output place, so that it doesn't need adding as a new node. - */ - if (node.data.parentNetNode.type === "input") { - inputPlaceIdsToAdd.delete(node.data.parentNetNode.id); - } else { - outputPlaceIdsToAdd.delete(node.data.parentNetNode.id); - } - } - - newNodes.push(node); - } - - if ( - inputPlaceIdsToAdd.size === 0 && - outputPlaceIdsToAdd.size === 0 && - nodesUnlinkedCount === 0 - ) { - /** - * No changes were necessary to the sub-process, so we return null. - */ - return null; - } - - if (minX === Number.POSITIVE_INFINITY) { - minX = 0; - } - if (maxX === Number.NEGATIVE_INFINITY) { - maxX = 300; - } - if (minYLeft === Number.POSITIVE_INFINITY) { - minYLeft = 0; - } - if (minYRight === Number.POSITIVE_INFINITY) { - minYRight = 0; - } - - for (const [index, inputPlaceId] of [...inputPlaceIdsToAdd].entries()) { - const id = generateUuid(); - - const label = inputPlaceLabelById[inputPlaceId]; - - if (!label) { - throw new Error(`Input place label not found for id ${inputPlaceId}`); - } - - newNodes.push({ - id, - type: "place", - position: { - x: minX, - y: minYLeft - (nodeDimensions.place.height + 80) * (index + 1), // stack above leftmost - }, - ...nodeDimensions.place, - data: { - label, - type: "place", - parentNetNode: { - id: inputPlaceId, - type: "input", - }, - }, - }); - } - - for (const [index, outputPlaceId] of [...outputPlaceIdsToAdd].entries()) { - const id = generateUuid(); - - const label = outputPlaceLabelById[outputPlaceId]; - - if (!label) { - throw new Error(`Output place label not found for id ${outputPlaceId}`); - } - - newNodes.push({ - id, - type: "place", - position: { - x: maxX, - y: minYRight - (nodeDimensions.place.height + 80) * (index + 1), // stack above rightmost - }, - ...nodeDimensions.place, - data: { - label, - type: "place", - parentNetNode: { - id: outputPlaceId, - type: "output", - }, - }, - }); - } - - return newNodes; -}; diff --git a/apps/hash-frontend/src/pages/process.page/process-editor-wrapper/use-process-save-and-load/use-persisted-nets.ts b/apps/hash-frontend/src/pages/process.page/process-editor-wrapper/use-process-save-and-load/use-persisted-nets.ts index fe733691f31..35a09c77203 100644 --- a/apps/hash-frontend/src/pages/process.page/process-editor-wrapper/use-process-save-and-load/use-persisted-nets.ts +++ b/apps/hash-frontend/src/pages/process.page/process-editor-wrapper/use-process-save-and-load/use-persisted-nets.ts @@ -1,24 +1,10 @@ import { useQuery } from "@apollo/client"; -import { - getIncomingLinkAndSourceEntities, - getOutgoingLinkAndTargetEntities, - getRoots, -} from "@blockprotocol/graph/stdlib"; -import type { Entity, LinkEntity } from "@blockprotocol/type-system"; -import type { - PetriNetDefinitionObject, - TransitionNodeData, -} from "@hashintel/petrinaut-old"; +import { getRoots } from "@blockprotocol/graph/stdlib"; +import type { SDCPN } from "@hashintel/petrinaut"; import { deserializeQueryEntitySubgraphResponse } from "@local/hash-graph-sdk/entity"; import { currentTimeInstantTemporalAxes } from "@local/hash-isomorphic-utils/graph-queries"; -import { - systemEntityTypes, - systemLinkEntityTypes, -} from "@local/hash-isomorphic-utils/ontology-type-ids"; -import type { - PetriNet, - SubProcessOf, -} from "@local/hash-isomorphic-utils/system-types/petrinet"; +import { systemEntityTypes } from "@local/hash-isomorphic-utils/ontology-type-ids"; +import type { PetriNet } from "@local/hash-isomorphic-utils/system-types/petrinet"; import { useMemo } from "react"; import type { @@ -37,124 +23,16 @@ export const getPersistedNetsFromSubgraph = ( const nets = getRoots(subgraph); - const childNetLinksByNodeIdAndChildNetId: PersistedNet["childNetLinksByNodeIdAndChildNetId"] = - {}; - return nets.map((net) => { const netTitle = net.properties["https://hash.ai/@h/types/property-type/title/"]; - const definition = net.properties[ - "https://hash.ai/@h/types/property-type/definition-object/" - ] as PetriNetDefinitionObject; - - const incomingSubProcesses = getIncomingLinkAndSourceEntities( - subgraph, - net.entityId, - ).filter( - ( - linkAndLeftEntity, - ): linkAndLeftEntity is { - linkEntity: LinkEntity[]; - leftEntity: Entity[]; - } => { - const linkEntity = linkAndLeftEntity.linkEntity[0]; - - if (!linkEntity) { - return false; - } - - return linkEntity.metadata.entityTypeIds.includes( - systemLinkEntityTypes.subProcessOf.linkEntityTypeId, - ); - }, - ); - - const transitionIdToSubprocess = new Map< - string, - TransitionNodeData["childNet"] - >(); - - for (const incomingSubProcess of incomingSubProcesses) { - const subProcessOfLink = incomingSubProcess.linkEntity[0]; - const subProcess = incomingSubProcess.leftEntity[0]; - - if (!subProcessOfLink || !subProcess) { - continue; - } - - childNetLinksByNodeIdAndChildNetId[ - subProcessOfLink.properties[ - "https://hash.ai/@h/types/property-type/transition-id/" - ] - ] = { - [subProcess.entityId]: { - linkEntityId: subProcessOfLink.entityId, - }, - }; - - transitionIdToSubprocess.set( - subProcessOfLink.properties[ - "https://hash.ai/@h/types/property-type/transition-id/" - ], - { - childNetTitle: - subProcess.properties[ - "https://hash.ai/@h/types/property-type/title/" - ], - childNetId: subProcess.entityId, - inputPlaceIds: - subProcessOfLink.properties[ - "https://hash.ai/@h/types/property-type/input-place-id/" - ], - outputPlaceIds: - subProcessOfLink.properties[ - "https://hash.ai/@h/types/property-type/output-place-id/" - ], - }, - ); - } - - const clonedNodes = JSON.parse(JSON.stringify(definition.nodes)); - - const clonedDefinition = { - ...definition, - nodes: clonedNodes, - }; - - for (const node of clonedDefinition.nodes) { - if (node.data.type === "transition") { - const subProcess = transitionIdToSubprocess.get(node.id); - - if (subProcess) { - node.data.subProcess = subProcess; - } - } - } - - const outgoingLinkAndRightEntities = getOutgoingLinkAndTargetEntities( - subgraph, - net.entityId, - ).filter( - ( - linkAndRightEntity, - ): linkAndRightEntity is { - linkEntity: LinkEntity[]; - rightEntity: Entity[]; - } => { - const linkEntity = linkAndRightEntity.linkEntity[0]; - - if (!linkEntity) { - return false; - } - - return linkEntity.metadata.entityTypeIds.includes( - systemLinkEntityTypes.subProcessOf.linkEntityTypeId, - ); - }, - ); + const rawDefinition = + net.properties[ + "https://hash.ai/@h/types/property-type/definition-object/" + ]; - const parentProcess = outgoingLinkAndRightEntities[0]?.rightEntity[0]; + const definition = rawDefinition as SDCPN; const userEditable = !!data.queryEntitySubgraph.entityPermissions?.[net.entityId]?.update; @@ -162,17 +40,7 @@ export const getPersistedNetsFromSubgraph = ( return { entityId: net.entityId, title: netTitle, - definition: clonedDefinition, - parentNet: parentProcess - ? { - parentNetId: parentProcess.entityId, - title: - parentProcess.properties[ - "https://hash.ai/@h/types/property-type/title/" - ], - } - : null, - childNetLinksByNodeIdAndChildNetId, + definition, userEditable, }; }); @@ -195,20 +63,7 @@ export const usePersistedNets = () => { }, ], }, - traversalPaths: [ - { - edges: [ - { kind: "has-left-entity", direction: "incoming" }, - { kind: "has-right-entity", direction: "outgoing" }, - ], - }, - { - edges: [ - { kind: "has-right-entity", direction: "incoming" }, - { kind: "has-left-entity", direction: "outgoing" }, - ], - }, - ], + traversalPaths: [], includeDrafts: false, temporalAxes: currentTimeInstantTemporalAxes, includePermissions: true, diff --git a/libs/@hashintel/petrinaut-old/.npmignore b/libs/@hashintel/petrinaut-old/.npmignore deleted file mode 100644 index eb376535262..00000000000 --- a/libs/@hashintel/petrinaut-old/.npmignore +++ /dev/null @@ -1,6 +0,0 @@ -## Ignore all files (but still allow sub-folder scanning) -* -!*/ - -## Allow-list for files and folders -!dist/** diff --git a/libs/@hashintel/petrinaut-old/LICENSE-APACHE.md b/libs/@hashintel/petrinaut-old/LICENSE-APACHE.md deleted file mode 100644 index 4b43328a923..00000000000 --- a/libs/@hashintel/petrinaut-old/LICENSE-APACHE.md +++ /dev/null @@ -1,189 +0,0 @@ -# Apache License - -_Version 2.0, January 2004_ -_<>_ - -### Terms and Conditions for use, reproduction, and distribution - -#### 1. Definitions - -“License” shall mean the terms and conditions for use, reproduction, and -distribution as defined by Sections 1 through 9 of this document. - -“Licensor” shall mean the copyright owner or entity authorized by the copyright -owner that is granting the License. - -“Legal Entity” shall mean the union of the acting entity and all other entities -that control, are controlled by, or are under common control with that entity. -For the purposes of this definition, “control” means **(i)** the power, direct or -indirect, to cause the direction or management of such entity, whether by -contract or otherwise, or **(ii)** ownership of fifty percent (50%) or more of the -outstanding shares, or **(iii)** beneficial ownership of such entity. - -“You” (or “Your”) shall mean an individual or Legal Entity exercising -permissions granted by this License. - -“Source” form shall mean the preferred form for making modifications, including -but not limited to software source code, documentation source, and configuration -files. - -“Object” form shall mean any form resulting from mechanical transformation or -translation of a Source form, including but not limited to compiled object code, -generated documentation, and conversions to other media types. - -“Work” shall mean the work of authorship, whether in Source or Object form, made -available under the License, as indicated by a copyright notice that is included -in or attached to the work (an example is provided in the Appendix below). - -“Derivative Works” shall mean any work, whether in Source or Object form, that -is based on (or derived from) the Work and for which the editorial revisions, -annotations, elaborations, or other modifications represent, as a whole, an -original work of authorship. For the purposes of this License, Derivative Works -shall not include works that remain separable from, or merely link (or bind by -name) to the interfaces of, the Work and Derivative Works thereof. - -“Contribution” shall mean any work of authorship, including the original version -of the Work and any modifications or additions to that Work or Derivative Works -thereof, that is intentionally submitted to Licensor for inclusion in the Work -by the copyright owner or by an individual or Legal Entity authorized to submit -on behalf of the copyright owner. For the purposes of this definition, -“submitted” means any form of electronic, verbal, or written communication sent -to the Licensor or its representatives, including but not limited to -communication on electronic mailing lists, source code control systems, and -issue tracking systems that are managed by, or on behalf of, the Licensor for -the purpose of discussing and improving the Work, but excluding communication -that is conspicuously marked or otherwise designated in writing by the copyright -owner as “Not a Contribution.” - -“Contributor” shall mean Licensor and any individual or Legal Entity on behalf -of whom a Contribution has been received by Licensor and subsequently -incorporated within the Work. - -#### 2. Grant of Copyright License - -Subject to the terms and conditions of this License, each Contributor hereby -grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, -irrevocable copyright license to reproduce, prepare Derivative Works of, -publicly display, publicly perform, sublicense, and distribute the Work and such -Derivative Works in Source or Object form. - -#### 3. Grant of Patent License - -Subject to the terms and conditions of this License, each Contributor hereby -grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, -irrevocable (except as stated in this section) patent license to make, have -made, use, offer to sell, sell, import, and otherwise transfer the Work, where -such license applies only to those patent claims licensable by such Contributor -that are necessarily infringed by their Contribution(s) alone or by combination -of their Contribution(s) with the Work to which such Contribution(s) was -submitted. If You institute patent litigation against any entity (including a -cross-claim or counterclaim in a lawsuit) alleging that the Work or a -Contribution incorporated within the Work constitutes direct or contributory -patent infringement, then any patent licenses granted to You under this License -for that Work shall terminate as of the date such litigation is filed. - -#### 4. Redistribution - -You may reproduce and distribute copies of the Work or Derivative Works thereof -in any medium, with or without modifications, and in Source or Object form, -provided that You meet the following conditions: - -- **(a)** You must give any other recipients of the Work or Derivative Works a copy of - this License; and -- **(b)** You must cause any modified files to carry prominent notices stating that You - changed the files; and -- **(c)** You must retain, in the Source form of any Derivative Works that You distribute, - all copyright, patent, trademark, and attribution notices from the Source form - of the Work, excluding those notices that do not pertain to any part of the - Derivative Works; and -- **(d)** If the Work includes a “NOTICE” text file as part of its distribution, then any - Derivative Works that You distribute must include a readable copy of the - attribution notices contained within such NOTICE file, excluding those notices - that do not pertain to any part of the Derivative Works, in at least one of the - following places: within a NOTICE text file distributed as part of the - Derivative Works; within the Source form or documentation, if provided along - with the Derivative Works; or, within a display generated by the Derivative - Works, if and wherever such third-party notices normally appear. The contents of - the NOTICE file are for informational purposes only and do not modify the - License. You may add Your own attribution notices within Derivative Works that - You distribute, alongside or as an addendum to the NOTICE text from the Work, - provided that such additional attribution notices cannot be construed as - modifying the License. - -You may add Your own copyright statement to Your modifications and may provide -additional or different license terms and conditions for use, reproduction, or -distribution of Your modifications, or for any such Derivative Works as a whole, -provided Your use, reproduction, and distribution of the Work otherwise complies -with the conditions stated in this License. - -#### 5. Submission of Contributions - -Unless You explicitly state otherwise, any Contribution intentionally submitted -for inclusion in the Work by You to the Licensor shall be under the terms and -conditions of this License, without any additional terms or conditions. -Notwithstanding the above, nothing herein shall supersede or modify the terms of -any separate license agreement you may have executed with Licensor regarding -such Contributions. - -#### 6. Trademarks - -This License does not grant permission to use the trade names, trademarks, -service marks, or product names of the Licensor, except as required for -reasonable and customary use in describing the origin of the Work and -reproducing the content of the NOTICE file. - -#### 7. Disclaimer of Warranty - -Unless required by applicable law or agreed to in writing, Licensor provides the -Work (and each Contributor provides its Contributions) on an “AS IS” BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, -including, without limitation, any warranties or conditions of TITLE, -NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are -solely responsible for determining the appropriateness of using or -redistributing the Work and assume any risks associated with Your exercise of -permissions under this License. - -#### 8. Limitation of Liability - -In no event and under no legal theory, whether in tort (including negligence), -contract, or otherwise, unless required by applicable law (such as deliberate -and grossly negligent acts) or agreed to in writing, shall any Contributor be -liable to You for damages, including any direct, indirect, special, incidental, -or consequential damages of any character arising as a result of this License or -out of the use or inability to use the Work (including but not limited to -damages for loss of goodwill, work stoppage, computer failure or malfunction, or -any and all other commercial damages or losses), even if such Contributor has -been advised of the possibility of such damages. - -#### 9. Accepting Warranty or Additional Liability - -While redistributing the Work or Derivative Works thereof, You may choose to -offer, and charge a fee for, acceptance of support, warranty, indemnity, or -other liability obligations and/or rights consistent with this License. However, -in accepting such obligations, You may act only on Your own behalf and on Your -sole responsibility, not on behalf of any other Contributor, and only if You -agree to indemnify, defend, and hold each Contributor harmless for any liability -incurred by, or claims asserted against, such Contributor by reason of your -accepting any such warranty or additional liability. - -_END OF TERMS AND CONDITIONS_ - -### APPENDIX: Apply the Apache License to a specific file - -To apply the Apache License to an individual file, attach the following notice. -The text should be enclosed in the appropriate comment syntax for the file -format. - - Copyright © 2025–, HASH - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. diff --git a/libs/@hashintel/petrinaut-old/LICENSE-MIT.md b/libs/@hashintel/petrinaut-old/LICENSE-MIT.md deleted file mode 100644 index d85585ee20d..00000000000 --- a/libs/@hashintel/petrinaut-old/LICENSE-MIT.md +++ /dev/null @@ -1,21 +0,0 @@ -# MIT License - -Copyright © 2025–, HASH - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/libs/@hashintel/petrinaut-old/LICENSE.md b/libs/@hashintel/petrinaut-old/LICENSE.md deleted file mode 100644 index 6dad94c0e5a..00000000000 --- a/libs/@hashintel/petrinaut-old/LICENSE.md +++ /dev/null @@ -1,3 +0,0 @@ -# License - -Licensed under either of the [Apache License, Version 2.0](LICENSE-APACHE.md) or [MIT license](LICENSE-MIT.md) at your option. diff --git a/libs/@hashintel/petrinaut-old/README.md b/libs/@hashintel/petrinaut-old/README.md deleted file mode 100644 index b54cba89386..00000000000 --- a/libs/@hashintel/petrinaut-old/README.md +++ /dev/null @@ -1,39 +0,0 @@ - -# Petrinaut - -A component for editing [**Petri nets**](https://en.wikipedia.org/wiki/Petri_net). - -Currently **under development** and not ready for usage. - -## Development Mode - -For development and testing, you can use the included dev mode: - -```bash -yarn dev -``` - -This will start a development server with a fully functional Petrinaut editor that uses local storage to persist created nets. - -## Usage - -The component currently depends on a specific MUI theme and any consuming application should wrap it as follows: - -```tsx -import { CacheProvider } from "@emotion/react"; -import { ThemeProvider } from "@mui/material/styles"; -import { createEmotionCache, theme } from "@hashintel/design-system/theme"; - -const emotionCache = createEmotionCache(); - -const App = () => { - - return ( - - - - - - ) -} -``` diff --git a/libs/@hashintel/petrinaut-old/dev/dev-app.tsx b/libs/@hashintel/petrinaut-old/dev/dev-app.tsx deleted file mode 100644 index 67a9ace90cf..00000000000 --- a/libs/@hashintel/petrinaut-old/dev/dev-app.tsx +++ /dev/null @@ -1,12 +0,0 @@ -import { StrictMode } from "react"; -import { createRoot } from "react-dom/client"; - -import { DevWrapper } from "./dev-wrapper"; - -const root = createRoot(document.getElementById("root") as HTMLElement); - -root.render( - - - , -); diff --git a/libs/@hashintel/petrinaut-old/dev/dev-wrapper.tsx b/libs/@hashintel/petrinaut-old/dev/dev-wrapper.tsx deleted file mode 100644 index c2355b34e82..00000000000 --- a/libs/@hashintel/petrinaut-old/dev/dev-wrapper.tsx +++ /dev/null @@ -1,166 +0,0 @@ -import { CacheProvider } from "@emotion/react"; -import { - createEmotionCache, - fluidTypographyStyles, - theme, -} from "@hashintel/design-system/theme"; -import { useLocalStorage } from "@mantine/hooks"; -import { ThemeProvider } from "@mui/material"; -import { produce } from "immer"; -import { useCallback, useEffect, useMemo, useState } from "react"; - -import { Petrinaut } from "../src/petrinaut"; -import type { - MinimalNetMetadata, - PetriNetDefinitionObject, -} from "../src/petrinaut/types"; - -const emotionCache = createEmotionCache(); - -type StoredNet = { - id: string; - title: string; - petriNetDefinition: PetriNetDefinitionObject; - createdAt: string; -}; - -export const DevWrapper = () => { - const [storedNets, setStoredNets] = useLocalStorage({ - key: "petrinaut-dev-nets", - defaultValue: [], - getInitialValueInEffect: false, - }); - - const [currentNetId, setCurrentNetId] = useState(null); - - const currentNet = useMemo(() => { - if (!currentNetId) { - return null; - } - return storedNets.find((net) => net.id === currentNetId) ?? null; - }, [currentNetId, storedNets]); - - const existingNets: MinimalNetMetadata[] = useMemo(() => { - return storedNets - .filter((net) => net.id !== currentNetId) - .map((net) => ({ - netId: net.id, - title: net.title, - })); - }, [currentNetId, storedNets]); - - const createNewNet = useCallback( - (params: { - petriNetDefinition: PetriNetDefinitionObject; - title: string; - }) => { - const newNet: StoredNet = { - id: `net-${Date.now()}`, - title: params.title, - petriNetDefinition: params.petriNetDefinition, - createdAt: new Date().toISOString(), - }; - - setStoredNets((prev) => [...prev, newNet]); - setCurrentNetId(newNet.id); - }, - [setStoredNets], - ); - - const loadPetriNet = useCallback((petriNetId: string) => { - setCurrentNetId(petriNetId); - }, []); - - const setTitle = useCallback( - (title: string) => { - if (!currentNetId) { - return; - } - - setStoredNets((prev) => - prev.map((net) => (net.id === currentNetId ? { ...net, title } : net)), - ); - }, - [currentNetId, setStoredNets], - ); - - const mutatePetriNetDefinition = useCallback( - (definitionMutationFn: (draft: PetriNetDefinitionObject) => void) => { - if (!currentNetId) { - return; - } - - setStoredNets((prev) => - prev.map((net) => { - if (net.id === currentNetId) { - const newDefinition = produce( - net.petriNetDefinition, - definitionMutationFn, - ); - return { ...net, petriNetDefinition: newDefinition }; - } - return net; - }), - ); - }, - [currentNetId, setStoredNets], - ); - - // Initialize with a default net if none exists - useEffect(() => { - if (!storedNets[0]) { - createNewNet({ - petriNetDefinition: { - arcs: [], - nodes: [], - tokenTypes: [], - }, - title: "New Process", - }); - } else if (!currentNetId) { - setCurrentNetId(storedNets[0].id); - } - }, [storedNets.length, currentNetId, createNewNet, storedNets]); - - if (!currentNet) { - return ( - - -
-

Petrinaut Dev Mode

-

No net selected. Create a new net to get started.

-
-
-
- ); - } - - return ( - - - -
- -
-
-
- ); -}; diff --git a/libs/@hashintel/petrinaut-old/dev/index.html b/libs/@hashintel/petrinaut-old/dev/index.html deleted file mode 100644 index d95b457c1eb..00000000000 --- a/libs/@hashintel/petrinaut-old/dev/index.html +++ /dev/null @@ -1,31 +0,0 @@ - - - - - - Petrinaut Dev Mode - - - -
- - - diff --git a/libs/@hashintel/petrinaut-old/dev/tsconfig.json b/libs/@hashintel/petrinaut-old/dev/tsconfig.json deleted file mode 100644 index 42a456f130a..00000000000 --- a/libs/@hashintel/petrinaut-old/dev/tsconfig.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "extends": "../tsconfig.json", - "compilerOptions": { - "moduleResolution": "bundler", - "jsx": "react-jsx" - }, - "include": ["./*.tsx", "./*.ts"] -} diff --git a/libs/@hashintel/petrinaut-old/dev/vite.config.ts b/libs/@hashintel/petrinaut-old/dev/vite.config.ts deleted file mode 100644 index 8681959d659..00000000000 --- a/libs/@hashintel/petrinaut-old/dev/vite.config.ts +++ /dev/null @@ -1,11 +0,0 @@ -import react from "@vitejs/plugin-react"; -import { defineConfig } from "vite"; - -export default defineConfig({ - mode: "development", - plugins: [react()], - root: "dev", - server: { - open: true, - }, -}); diff --git a/libs/@hashintel/petrinaut-old/eslint.config.js b/libs/@hashintel/petrinaut-old/eslint.config.js deleted file mode 100644 index c64e56f76ea..00000000000 --- a/libs/@hashintel/petrinaut-old/eslint.config.js +++ /dev/null @@ -1,58 +0,0 @@ -import { createBase } from "@local/eslint/deprecated"; - -export default [ - ...createBase(import.meta.dirname), - { - languageOptions: { - parserOptions: { - projectService: { - allowDefaultProject: [ - "assets.d.ts", - "panda.config.ts", - "postcss.config.cjs", - ], - }, - tsconfigRootDir: import.meta.dirname, - }, - }, - }, - { - files: ["dev/**/*"], - languageOptions: { - parserOptions: { - projectService: { - defaultProject: "./dev/tsconfig.json", - }, - tsconfigRootDir: import.meta.dirname, - }, - }, - }, - { - files: ["dev/*.tsx"], - rules: { - "import/no-extraneous-dependencies": ["error", { devDependencies: true }], - }, - }, - { - rules: { - // Disabled because React Compiler handles optimization automatically - "react/jsx-no-bind": "off", - "no-restricted-imports": [ - "error", - { - patterns: [ - { - group: ["@mui/material/*"], - message: "Please import from @mui/material instead", - }, - { - group: ["@local/*"], - message: - "You cannot use unpublished local packages in a published package.", - }, - ], - }, - ], - }, - }, -]; diff --git a/libs/@hashintel/petrinaut-old/package.json b/libs/@hashintel/petrinaut-old/package.json deleted file mode 100644 index 49167ff30f1..00000000000 --- a/libs/@hashintel/petrinaut-old/package.json +++ /dev/null @@ -1,58 +0,0 @@ -{ - "name": "@hashintel/petrinaut-old", - "version": "0.0.5", - "private": true, - "description": "A visual editor for Petri nets", - "license": "(MIT OR Apache-2.0)", - "repository": { - "type": "git", - "url": "https://github.com/hashintel/hash.git", - "directory": "libs/@hashintel/petrinaut-old" - }, - "type": "module", - "sideEffects": false, - "main": "src/main.ts", - "types": "src/main.ts", - "scripts": { - "dev": "vite --config dev/vite.config.ts", - "fix:eslint": "eslint --fix .", - "lint:eslint": "eslint --report-unused-disable-directives .", - "lint:tsc": "tsc", - "prepublishOnly": "PACKAGE_DIR=$(pwd) yarn workspace @local/repo-chores exe scripts/prepublish.ts", - "postpublish": "PACKAGE_DIR=$(pwd) yarn workspace @local/repo-chores exe scripts/postpublish.ts" - }, - "dependencies": { - "@emotion/react": "11.14.0", - "@fortawesome/free-solid-svg-icons": "6.7.2", - "@handsontable/react": "16.1.1", - "@hashintel/block-design-system": "workspace:*", - "@hashintel/design-system": "workspace:*", - "@mantine/hooks": "8.3.5", - "@mui/material": "5.18.0", - "@mui/system": "5.18.0", - "elkjs": "0.11.0", - "reactflow": "11.11.4", - "uuid": "13.0.0", - "zustand": "5.0.8" - }, - "devDependencies": { - "@emotion/react": "11.14.0", - "@local/eslint": "workspace:*", - "@mui/material": "5.18.0", - "@mui/system": "5.18.0", - "@types/react": "19.2.7", - "@types/react-dom": "19.2.3", - "@vitejs/plugin-react": "5.0.4", - "eslint": "9.39.2", - "immer": "10.1.3", - "react": "19.2.3", - "react-dom": "19.2.3", - "typescript": "5.9.3", - "vite": "7.1.11", - "web-worker": "1.4.1" - }, - "peerDependencies": { - "react": "^19.0.0", - "react-dom": "^19.0.0" - } -} diff --git a/libs/@hashintel/petrinaut-old/src/main.ts b/libs/@hashintel/petrinaut-old/src/main.ts deleted file mode 100644 index fa5b406eb94..00000000000 --- a/libs/@hashintel/petrinaut-old/src/main.ts +++ /dev/null @@ -1 +0,0 @@ -export * from "./petrinaut"; diff --git a/libs/@hashintel/petrinaut-old/src/petrinaut.tsx b/libs/@hashintel/petrinaut-old/src/petrinaut.tsx deleted file mode 100644 index 67c510c6637..00000000000 --- a/libs/@hashintel/petrinaut-old/src/petrinaut.tsx +++ /dev/null @@ -1,742 +0,0 @@ -import "reactflow/dist/style.css"; - -import { Box, Button, Stack } from "@mui/material"; -import type { DragEvent } from "react"; -import { useCallback, useEffect, useMemo, useRef, useState } from "react"; -import type { - Connection, - Node, - NodeChange, - ReactFlowInstance, -} from "reactflow"; -import ReactFlow, { - Background, - ConnectionLineType, - ReactFlowProvider, - useReactFlow, -} from "reactflow"; - -import { applyNodeChanges } from "./petrinaut/apply-node-changes"; -import { Arc } from "./petrinaut/arc"; -import { ArcEditor } from "./petrinaut/arc-editor"; -import { createArc } from "./petrinaut/create-arc"; -import { - EditorContextProvider, - type MutatePetriNetDefinition, - useEditorContext, -} from "./petrinaut/editor-context"; -import { exampleCPN } from "./petrinaut/examples"; -import { generateUuid } from "./petrinaut/generate-uuid"; -import { LogPane } from "./petrinaut/log-pane"; -import { PlaceEditor } from "./petrinaut/place-editor"; -import { PlaceNode } from "./petrinaut/place-node"; -import { Sidebar } from "./petrinaut/sidebar"; -import { SimulationContextProvider } from "./petrinaut/simulation-context"; -import { SimulationControls } from "./petrinaut/simulation-controls"; -import { nodeDimensions } from "./petrinaut/styling"; -import { TitleAndNetSelect } from "./petrinaut/title-and-net-select"; -import { TokenTypes } from "./petrinaut/token-types"; -import { defaultTokenTypes } from "./petrinaut/token-types/default-token-types"; -import { TransitionEditor } from "./petrinaut/transition-editor"; -import { TransitionNode } from "./petrinaut/transition-node"; -import type { - ArcData, - ArcType, - MinimalNetMetadata, - NodeData, - NodeType, - ParentNet, - PetriNetDefinitionObject, - PlaceNodeData, - PlaceNodeType, - TokenCounts, - TokenType, - TransitionCondition, - TransitionNodeData, - TransitionNodeType, -} from "./petrinaut/types"; -import { useConvertToPnml } from "./petrinaut/use-convert-to-pnml"; -import { useLoadFromPnml } from "./petrinaut/use-load-from-pnml"; - -export type { - ArcData, - ArcType, - MinimalNetMetadata, - NodeData, - NodeType, - ParentNet, - PetriNetDefinitionObject, - PlaceNodeData, - PlaceNodeType, - TokenCounts, - TokenType, - TransitionCondition, - TransitionNodeData, - TransitionNodeType, -}; - -export { nodeDimensions }; - -export { defaultTokenTypes }; - -export { NetSelector } from "./petrinaut/net-selector"; - -type DraggingStateByNodeId = Record< - string, - { dragging: boolean; position: { x: number; y: number } } ->; - -const PetrinautInner = ({ - hideNetManagementControls, -}: { hideNetManagementControls: boolean }) => { - const canvasContainer = useRef(null); - - const [reactFlowInstance, setReactFlowInstance] = useState | null>(null); - - const { createNewNet, petriNetDefinition, mutatePetriNetDefinition, title } = - useEditorContext(); - - const [selectedPlacePosition, setSelectedPlacePosition] = useState<{ - x: number; - y: number; - } | null>(null); - - const [selectedPlaceId, setSelectedPlaceId] = useState(null); - const [selectedTransition, setSelectedTransition] = useState( - null, - ); - - const [selectedArc, setSelectedArc] = useState< - (ArcType & { position: { x: number; y: number } }) | null - >(null); - - /** - * While a node is being dragged, we don't want to keep reporting position changes to the consumer, - * but we need to track the fact it's being dragged and where it is currently for reactflow to use. - * This state tracks that information. - */ - const [draggingStateByNodeId, setDraggingStateByNodeId] = - useState({}); - - useEffect(() => { - setDraggingStateByNodeId({}); - }, [petriNetDefinition.nodes]); - - const nodeTypes = useMemo( - () => ({ - place: PlaceNode, - transition: TransitionNode, - }), - [], - ); - - const edgeTypes = useMemo( - () => ({ - default: Arc, - }), - [], - ); - - const { setViewport } = useReactFlow(); - - useEffect(() => { - setViewport({ x: 0, y: 0, zoom: 1 }); - }, [setViewport]); - - const onNodesChange = useCallback( - (changes: NodeChange[]) => { - applyNodeChanges({ - changes, - draggingStateByNodeId, - mutatePetriNetDefinition, - setDraggingStateByNodeId, - }); - }, - [draggingStateByNodeId, mutatePetriNetDefinition], - ); - - const onEdgesChange = useCallback(() => { - /** - * There are no edge changes we need to process at the moment: - * - We add arcs in onConnect, we won't don't process 'add' - * - We don't allow removing arcs at the moment - * - We handle selection in separate state when an edge is clicked - * - Unclear what 'reset' is supposed to do - */ - }, []); - - const isValidConnection = useCallback( - (connection: Connection) => { - const sourceNode = petriNetDefinition.nodes.find( - (node) => node.id === connection.source, - ); - const targetNode = petriNetDefinition.nodes.find( - (node) => node.id === connection.target, - ); - - if (!sourceNode || !targetNode) { - return false; - } - - // Places can only connect to transitions and vice versa - return sourceNode.type !== targetNode.type; - }, - [petriNetDefinition.nodes], - ); - - const onConnect = useCallback( - (connection: Connection) => { - if (isValidConnection(connection)) { - const newArc: ArcType = { - ...connection, - id: `arc__${connection.source}-${connection.target}`, - source: connection.source ?? "", - target: connection.target ?? "", - type: "default", - data: { - tokenWeights: { [petriNetDefinition.tokenTypes[0]!.id]: 1 }, - }, - interactionWidth: 8, - }; - - const addedArc = createArc(newArc, petriNetDefinition.arcs); - - if (!addedArc) { - return; - } - - mutatePetriNetDefinition((existingNet) => { - existingNet.arcs.push(addedArc); - }); - } - }, - [ - petriNetDefinition.arcs, - isValidConnection, - petriNetDefinition.tokenTypes, - mutatePetriNetDefinition, - ], - ); - - const onInit = useCallback((instance: ReactFlowInstance) => { - setReactFlowInstance(instance); - }, []); - - const onDragOver = useCallback((event: DragEvent) => { - event.preventDefault(); - // eslint-disable-next-line no-param-reassign - event.dataTransfer.dropEffect = "move"; - }, []); - - const onDrop = useCallback( - (event: DragEvent) => { - event.preventDefault(); - - if (!reactFlowInstance || !canvasContainer.current) { - return; - } - - const reactFlowBounds = canvasContainer.current.getBoundingClientRect(); - const nodeType = event.dataTransfer.getData("application/reactflow") as - | "place" - | "transition"; - - const { width, height } = nodeDimensions[nodeType]; - - const position = reactFlowInstance.project({ - x: event.clientX - reactFlowBounds.left - width / 2, - y: event.clientY - reactFlowBounds.top - height / 2, - }); - - const newNode: NodeType = { - id: `${nodeType}__${generateUuid()}`, - type: nodeType, - position, - ...nodeDimensions[nodeType], - data: { - label: `${nodeType} ${petriNetDefinition.nodes.length + 1}`, - ...(nodeType === "place" - ? { - type: "place", - tokenCounts: {}, - } - : { type: "transition" }), - }, - }; - - mutatePetriNetDefinition((existingNet) => { - existingNet.nodes.push(newNode); - }); - }, - [reactFlowInstance, petriNetDefinition.nodes, mutatePetriNetDefinition], - ); - - const onNodeClick = useCallback( - (event: React.MouseEvent, node: Node) => { - if (selectedPlaceId && selectedPlaceId === node.id) { - return; - } - - setSelectedPlaceId(null); - setSelectedPlacePosition(null); - setSelectedArc(null); - setSelectedTransition(null); - - if (node.type === "place") { - setSelectedPlaceId(node.id); - setSelectedPlacePosition({ x: event.clientX, y: event.clientY }); - } else if (node.type === "transition") { - setSelectedTransition(node.id); - } - }, - [selectedPlaceId], - ); - - const handleUpdateInitialTokens = useCallback( - (nodeId: string, initialTokenCounts: TokenCounts) => { - mutatePetriNetDefinition((existingNet) => { - for (const node of existingNet.nodes) { - if (node.id === nodeId && node.data.type === "place") { - // @todo don't overwrite the whole object, update what has changed only - node.data.initialTokenCounts = initialTokenCounts; - return; - } - } - }); - }, - [mutatePetriNetDefinition], - ); - - const handleUpdateNodeLabel = useCallback( - (nodeId: string, label: string) => { - mutatePetriNetDefinition((existingNet) => { - for (const node of existingNet.nodes) { - if (node.id === nodeId) { - node.data.label = label; - return; - } - } - }); - }, - [mutatePetriNetDefinition], - ); - - const onEdgeClick = useCallback((event: React.MouseEvent, edge: ArcType) => { - event.stopPropagation(); - - setSelectedArc({ - ...edge, - position: { x: event.clientX, y: event.clientY }, - }); - }, []); - - const handleUpdateEdgeWeight = useCallback( - ( - edgeId: string, - tokenWeights: { [tokenTypeId: string]: number | undefined }, - ) => { - mutatePetriNetDefinition((existingNet) => { - for (const arc of existingNet.arcs) { - if (arc.id === edgeId) { - // @todo don't overwrite the whole object, update what has changed only - arc.data ??= { tokenWeights: {} }; - arc.data.tokenWeights = tokenWeights; - return; - } - } - }); - }, - [mutatePetriNetDefinition], - ); - - const handlePaneClick = useCallback(() => { - setSelectedPlaceId(null); - setSelectedTransition(null); - setSelectedArc(null); - }, []); - - const handleUpdateTransition = useCallback( - ( - transitionId: string, - transitionData: Omit, - ) => { - mutatePetriNetDefinition((existingNet) => { - const transitionNode = existingNet.nodes.find( - (node): node is TransitionNodeType => - node.id === transitionId && node.type === "transition", - ); - - if (!transitionNode) { - throw new Error(`Transition node with id ${transitionId} not found`); - } - - if (transitionData.label !== transitionNode.data.label) { - transitionNode.data.label = transitionData.label; - } - - if (transitionData.description !== transitionNode.data.description) { - transitionNode.data.description = transitionData.description; - } - - if (transitionData.delay !== transitionNode.data.delay) { - transitionNode.data.delay = transitionData.delay; - } - - if (transitionData.childNet !== transitionNode.data.childNet) { - // @todo check equality of nested fields - transitionNode.data.childNet = transitionData.childNet; - } - - if (transitionData.conditions !== transitionNode.data.conditions) { - // @todo check equality of nested fields - transitionNode.data.conditions = transitionData.conditions; - } - }); - }, - [mutatePetriNetDefinition], - ); - - const handleLoadExample = useCallback(() => { - createNewNet({ - petriNetDefinition: { - arcs: exampleCPN.arcs, - nodes: exampleCPN.nodes, - tokenTypes: exampleCPN.tokenTypes, - }, - title: exampleCPN.title, - }); - }, [createNewNet]); - - const convertToPnml = useConvertToPnml({ - petriNet: petriNetDefinition, - title, - }); - - const handleExport = () => { - const pnml = convertToPnml(); - - const blob = new Blob([pnml], { type: "application/xml" }); - - const url = URL.createObjectURL(blob); - - const a = document.createElement("a"); - a.href = url; - a.download = "process.pnml"; - - document.body.appendChild(a); - a.click(); - - setTimeout(() => { - document.body.removeChild(a); - URL.revokeObjectURL(url); - }, 0); - }; - - const loadFromPnml = useLoadFromPnml({ - createNewNet, - }); - - const handleLoadFromPnml = useCallback( - (event: React.ChangeEvent) => { - const file = event.target.files?.[0]; - if (!file) { - return; - } - - const reader = new FileReader(); - reader.onload = (readerEvent) => { - const contents = readerEvent.target?.result; - if (typeof contents === "string") { - loadFromPnml(contents); - } - }; - reader.readAsText(file); - }, - [loadFromPnml], - ); - - const fileInputRef = useRef(null); - - const handleImportClick = () => { - fileInputRef.current?.click(); - }; - - const selectedPlace = useMemo(() => { - if (!selectedPlaceId) { - return null; - } - - const place = petriNetDefinition.nodes.find( - (node): node is PlaceNodeType => - node.id === selectedPlaceId && node.data.type === "place", - ); - - if (!place) { - throw new Error(`Cannot find place with id ${selectedPlaceId}`); - } - - return place; - }, [petriNetDefinition.nodes, selectedPlaceId]); - - const nodesForReactFlow = useMemo(() => { - return petriNetDefinition.nodes.map((node) => { - const draggingState = draggingStateByNodeId[node.id]; - - return { - ...node, - // Fold in dragging state (the consumer isn't aware of it, as it's a transient property) - dragging: draggingState?.dragging ?? false, - position: draggingState?.dragging - ? draggingState.position - : node.position, - }; - }); - }, [petriNetDefinition.nodes, draggingStateByNodeId]); - - return ( - - {!hideNetManagementControls && } - - - - - - - - - - - {selectedTransition && ( - setSelectedTransition(null)} - transitionId={selectedTransition} - outgoingEdges={petriNetDefinition.arcs - .filter((edge) => edge.source === selectedTransition) - .map((edge) => { - const targetNode = petriNetDefinition.nodes.find( - (node) => node.id === edge.target, - ); - return { - id: edge.id, - source: edge.source, - target: edge.target, - targetLabel: targetNode?.data.label ?? "Unknown", - tokenWeights: edge.data?.tokenWeights ?? {}, - }; - })} - onUpdateTransition={handleUpdateTransition} - /> - )} - - {selectedPlace && ( - setSelectedPlaceId(null)} - onUpdateInitialTokens={handleUpdateInitialTokens} - onUpdateNodeLabel={handleUpdateNodeLabel} - /> - )} - - {selectedArc && ( - setSelectedArc(null)} - onUpdateWeights={handleUpdateEdgeWeight} - /> - )} - - - - - - - - {!hideNetManagementControls && ( - - - - - - - - )} - - - - ); -}; - -export type PetrinautProps = { - /** - * Nets other than this one which are available for selection, e.g. to switch to or to link from a transition. - */ - existingNets: MinimalNetMetadata[]; - /** - * Create a new net and load it into the editor. - */ - createNewNet: (params: { - petriNetDefinition: PetriNetDefinitionObject; - title: string; - }) => void; - /** - * Whether to hide controls relating to net loading, creation and title setting. - */ - hideNetManagementControls: boolean; - /** - * Minimal metadata on the net which this net is a child of, if any. - */ - parentNet: ParentNet | null; - /** - * The ID of the net which is currently loaded. - */ - petriNetId: string | null; - /** - * The definition of the net which is currently loaded. - */ - petriNetDefinition: PetriNetDefinitionObject; - /** - * Update the definition of the net which is currently loaded, by mutation. - * - * Should not return anything – the consumer of Petrinaut must pass mutationFn into an appropriate helper - * which does something with the mutated object, e.g. `immer`'s `produce` or `@automerge/react`'s `changeDoc`. - * - * @example - * mutatePetriNetDefinition((petriNetDefinition) => { - * petriNetDefinition.nodes.push({ - * id: "new-node", - * type: "place", - * position: { x: 0, y: 0 }, - * }); - * }); - * - * @see https://immerjs.github.io/immer - * @see https://automerge.org - */ - mutatePetriNetDefinition: MutatePetriNetDefinition; - /** - * Load a new net by id. - */ - loadPetriNet: (petriNetId: string) => void; - /** - * Set the title of the net which is currently loaded. - */ - setTitle: (title: string) => void; - /** - * The title of the net which is currently loaded. - */ - title: string; -}; - -export const Petrinaut = ({ - createNewNet, - existingNets, - hideNetManagementControls, - parentNet, - petriNetId, - petriNetDefinition, - mutatePetriNetDefinition, - loadPetriNet, - setTitle, - title, -}: PetrinautProps) => { - return ( - - - - - - - - ); -}; diff --git a/libs/@hashintel/petrinaut-old/src/petrinaut/apply-node-changes.ts b/libs/@hashintel/petrinaut-old/src/petrinaut/apply-node-changes.ts deleted file mode 100644 index 294619fc66e..00000000000 --- a/libs/@hashintel/petrinaut-old/src/petrinaut/apply-node-changes.ts +++ /dev/null @@ -1,118 +0,0 @@ -import type { Dispatch, SetStateAction } from "react"; -import type { NodeAddChange, NodeChange } from "reactflow"; - -import type { MutatePetriNetDefinition } from "./editor-context"; - -export type DraggingStateByNodeId = Record< - string, - { dragging: boolean; position: { x: number; y: number } } ->; - -/** - * A variant of reactflow's applyChange which mutates the petri net definition instead of creating a new node or edge array. - * - * @see https://github.com/xyflow/xyflow/blob/04055c9625cbd92cf83a2f4c340d6fae5199bfa3/packages/react/src/utils/changes.ts#L107 - */ -export const applyNodeChanges = ({ - changes, - draggingStateByNodeId, - mutatePetriNetDefinition, - setDraggingStateByNodeId, -}: { - changes: NodeChange[]; - draggingStateByNodeId: DraggingStateByNodeId; - mutatePetriNetDefinition: MutatePetriNetDefinition; - setDraggingStateByNodeId: Dispatch>; -}) => { - const changesByNodeId: Record = {}; - const addChanges: NodeAddChange[] = []; - - for (const change of changes) { - if ( - // We add nodes in onDrop, we won't handle these kind of changes - change.type === "add" || - // unclear what reset is supposed to do, it's not handled in reactflow's applyChange implementation - change.type === "reset" || - // We handle selection in separate state ourselves - change.type === "select" || - // We don't allow resizing at the moment - change.type === "dimensions" - ) { - continue; - } - - if (change.type === "position") { - if (change.dragging) { - setDraggingStateByNodeId((existing) => ({ - ...existing, - [change.id]: { - dragging: true, - position: change.position ?? { x: 0, y: 0 }, - }, - })); - } else { - const lastPosition = draggingStateByNodeId[change.id]?.position; - - if (!lastPosition) { - // we've had a dragging: false with no preceding dragging: true, so the node has not been dragged anywhere. - continue; - } - - /** - * When dragging stops, we receive a change event with 'dragging: false' but no position. - * We use the last position we received to report the change to the consumer. - */ - changesByNodeId[change.id] ??= []; - changesByNodeId[change.id]!.push({ - type: "position", - id: change.id, - position: lastPosition, - }); - - setDraggingStateByNodeId((existing) => ({ - ...existing, - [change.id]: { - dragging: false, - position: lastPosition, - }, - })); - } - } - } - - if (addChanges.length === 0 && Object.keys(changesByNodeId).length === 0) { - return; - } - - mutatePetriNetDefinition((existingNet) => { - for (const node of existingNet.nodes) { - const changesForNode: NodeChange[] = changesByNodeId[node.id] ?? []; - - for (const change of changesForNode) { - if (change.type === "position") { - if (change.position) { - if (node.position.x !== change.position.x) { - node.position.x = change.position.x; - } - if (node.position.y !== change.position.y) { - node.position.y = change.position.y; - } - } - - if (change.positionAbsolute) { - if (node.positionAbsolute?.x !== change.positionAbsolute.x) { - node.positionAbsolute ??= { x: 0, y: 0 }; - node.positionAbsolute.x = change.positionAbsolute.x; - } - if (node.positionAbsolute.y !== change.positionAbsolute.y) { - node.positionAbsolute ??= { x: 0, y: 0 }; - node.positionAbsolute.y = change.positionAbsolute.y; - } - } - } - } - } - - existingNet.nodes.push(...addChanges.map((change) => change.item)); - }); -}; diff --git a/libs/@hashintel/petrinaut-old/src/petrinaut/arc-editor.tsx b/libs/@hashintel/petrinaut-old/src/petrinaut/arc-editor.tsx deleted file mode 100644 index 336774a3c9c..00000000000 --- a/libs/@hashintel/petrinaut-old/src/petrinaut/arc-editor.tsx +++ /dev/null @@ -1,118 +0,0 @@ -import { TextField } from "@hashintel/design-system"; -import { Box, Card, Stack, Typography } from "@mui/material"; -import { useEffect, useMemo, useState } from "react"; -import { useReactFlow } from "reactflow"; - -import { useEditorContext } from "./editor-context"; - -interface ArcMenuProps { - arcId: string; - tokenWeights: { - [tokenTypeId: string]: number | undefined; - }; - position: { x: number; y: number }; - onClose: () => void; - onUpdateWeights: ( - arcId: string, - tokenWeights: { [tokenTypeId: string]: number | undefined }, - ) => void; -} - -export const ArcEditor = ({ - arcId, - tokenWeights, - position, - onClose: _onClose, - onUpdateWeights, -}: ArcMenuProps) => { - const [localWeights, setLocalWeights] = useState<{ - [tokenTypeId: string]: number | undefined; - }>(tokenWeights); - - const { getNodes, getEdges } = useReactFlow(); - - const { petriNetDefinition } = useEditorContext(); - - const direction = useMemo(() => { - const arc = getEdges().find((edge) => edge.id === arcId); - - const targetNode = getNodes().find((node) => node.id === arc?.target); - - if (!targetNode) { - return "in"; - } - - return targetNode.type === "transition" ? "in" : "out"; - }, [arcId, getEdges, getNodes]); - - useEffect(() => { - setLocalWeights(tokenWeights); - }, [tokenWeights]); - - const handleWeightChange = (tokenTypeId: string, value: string) => { - const parsedValue = Number.parseInt(value, 10); - const newWeight = Math.max(0, parsedValue || 0); - - const newWeights = { - ...localWeights, - [tokenTypeId]: newWeight, - }; - setLocalWeights(newWeights); - onUpdateWeights(arcId, newWeights); - }; - - return ( - - - - {direction === "in" ? "Tokens Required" : "Tokens Produced"} - - - {petriNetDefinition.tokenTypes.map((tokenType) => ( - - - - - {tokenType.name} - - - - handleWeightChange(tokenType.id, event.target.value) - } - size="small" - sx={{ width: 80 }} - /> - - ))} - - - - ); -}; diff --git a/libs/@hashintel/petrinaut-old/src/petrinaut/arc.tsx b/libs/@hashintel/petrinaut-old/src/petrinaut/arc.tsx deleted file mode 100644 index 77255a7cee6..00000000000 --- a/libs/@hashintel/petrinaut-old/src/petrinaut/arc.tsx +++ /dev/null @@ -1,188 +0,0 @@ -import { useCallback, useEffect, useState } from "react"; -import { getBezierPath, type Position } from "reactflow"; - -import { useEditorContext } from "./editor-context"; -import { generateUuid } from "./generate-uuid"; -import { useSimulationContext } from "./simulation-context"; -import type { TokenType } from "./types"; - -type AnimatingToken = { - id: string; - tokenTypeId: string; -}; - -export const Arc = ({ - id, - sourceX, - sourceY, - targetX, - targetY, - sourcePosition, - targetPosition, - data, -}: { - id: string; - sourceX: number; - sourceY: number; - targetX: number; - targetY: number; - sourcePosition: Position; - targetPosition: Position; - data?: { - tokenWeights: { - [tokenTypeId: string]: number; - }; - }; -}) => { - const { petriNetDefinition } = useEditorContext(); - - const { simulationSpeed } = useSimulationContext(); - - const [animatingTokens, setAnimatingTokens] = useState([]); - const [arcPath, labelX, labelY] = getBezierPath({ - sourceX, - sourceY, - sourcePosition, - targetX, - targetY, - targetPosition, - }); - - const addAnimatingToken = useCallback((tokenTypeId: string) => { - const newToken: AnimatingToken = { - id: generateUuid(), - tokenTypeId, - }; - - setAnimatingTokens((current) => [...current, newToken]); - }, []); - - /** - * Handle the event fired from SimulationContext to animate a token along the arcs of enabled transitions. - */ - useEffect(() => { - const handleTransitionFired = ( - event: CustomEvent<{ - arcId: string; - tokenTypeId: string; - }>, - ) => { - const { arcId, tokenTypeId } = event.detail; - if (arcId === id) { - addAnimatingToken(tokenTypeId); - } - }; - - window.addEventListener( - "animateTokenAlongArc", - handleTransitionFired as EventListener, - ); - - return () => { - window.removeEventListener( - "animateTokenAlongArc", - handleTransitionFired as EventListener, - ); - }; - }, [id, addAnimatingToken]); - - return ( - <> - - - {animatingTokens.map((token) => { - const tokenType = petriNetDefinition.tokenTypes.find( - (tt: TokenType) => tt.id === token.tokenTypeId, - ); - return ( - - - - ); - })} - - - {/* Show tokens required or produced */} - {Object.entries(data?.tokenWeights ?? {}) - .filter(([_, weight]) => weight > 0) - .map(([tokenTypeId, weight], index, nonZeroWeights) => { - const tokenType = petriNetDefinition.tokenTypes.find( - (tt: TokenType) => tt.id === tokenTypeId, - ); - - if (!tokenType) { - return null; - } - - const yOffset = (index - (nonZeroWeights.length - 1) / 2) * 20; - - return ( - - - - {weight} - - - ); - })} - - - ); -}; diff --git a/libs/@hashintel/petrinaut-old/src/petrinaut/create-arc.ts b/libs/@hashintel/petrinaut-old/src/petrinaut/create-arc.ts deleted file mode 100644 index 4542a97f424..00000000000 --- a/libs/@hashintel/petrinaut-old/src/petrinaut/create-arc.ts +++ /dev/null @@ -1,31 +0,0 @@ -import type { ArcType } from "./types"; - -const connectionExists = (arc: ArcType, arcs: ArcType[]) => { - return arcs.some( - (el) => - el.source === arc.source && - el.target === arc.target && - (el.sourceHandle === arc.sourceHandle || - (!el.sourceHandle && !arc.sourceHandle)) && - (el.targetHandle === arc.targetHandle || - (!el.targetHandle && !arc.targetHandle)), - ); -}; - -/** - * This is a variant of reactflow's `addEdge`. We use this so we can control adding the arc to the definition by mutation. - * addEdge otherwise creates a new array with the edge added via concat. - * - * We don't need a lot of the checking in there because we require a properly constructed ArcType to be passed in. - * - * @see https://github.com/xyflow/xyflow/blob/04055c9625cbd92cf83a2f4c340d6fae5199bfa3/packages/system/src/utils/edges/general.ts#L113 - * - * @returns ArcType if the arc shouod be added, null if it should not (because it already exists) - */ -export const createArc = (arc: ArcType, arcs: ArcType[]): ArcType | null => { - if (connectionExists(arc, arcs)) { - return null; - } - - return arc; -}; diff --git a/libs/@hashintel/petrinaut-old/src/petrinaut/editor-context.tsx b/libs/@hashintel/petrinaut-old/src/petrinaut/editor-context.tsx deleted file mode 100644 index a70182c29f9..00000000000 --- a/libs/@hashintel/petrinaut-old/src/petrinaut/editor-context.tsx +++ /dev/null @@ -1,117 +0,0 @@ -import { - createContext, - type Dispatch, - type SetStateAction, - useContext, - useMemo, - useState, -} from "react"; - -import type { - MinimalNetMetadata, - ParentNet, - PetriNetDefinitionObject, -} from "./types"; - -export type MutatePetriNetDefinition = ( - mutationFn: (petriNetDefinition: PetriNetDefinitionObject) => undefined, -) => void; - -type EditorContextValue = { - createNewNet: (params: { - petriNetDefinition: PetriNetDefinitionObject; - title: string; - }) => void; - existingNets: MinimalNetMetadata[]; - loadPetriNet: (petriNetId: string) => void; - parentNet: ParentNet | null; - petriNetId: string | null; - petriNetDefinition: PetriNetDefinitionObject; - readonly: boolean; - setParentNet: Dispatch>; - mutatePetriNetDefinition: MutatePetriNetDefinition; - setTitle: (title: string) => void; - title: string; -}; - -const EditorContext = createContext(undefined); - -type EditorContextProviderProps = { - children: React.ReactNode; - createNewNet: (params: { - petriNetDefinition: PetriNetDefinitionObject; - title: string; - }) => void; - existingNets: MinimalNetMetadata[]; - parentNet: ParentNet | null; - petriNetId: string | null; - petriNetDefinition: PetriNetDefinitionObject; - mutatePetriNetDefinition: MutatePetriNetDefinition; - loadPetriNet: (petriNetId: string) => void; - readonly: boolean; - setTitle: (title: string) => void; - title: string; -}; - -export const EditorContextProvider = ({ - children, - createNewNet, - existingNets, - parentNet: parentNetFromProps, - petriNetId, - petriNetDefinition, - readonly, - loadPetriNet, - mutatePetriNetDefinition, - setTitle, - title, -}: EditorContextProviderProps) => { - const [parentNet, setParentNet] = useState( - parentNetFromProps, - ); - - const value: EditorContextValue = useMemo( - () => ({ - createNewNet, - existingNets, - loadPetriNet, - parentNet, - petriNetId, - petriNetDefinition, - readonly, - setParentNet, - mutatePetriNetDefinition, - setTitle, - title, - }), - [ - createNewNet, - existingNets, - loadPetriNet, - mutatePetriNetDefinition, - parentNet, - petriNetId, - petriNetDefinition, - readonly, - setParentNet, - setTitle, - title, - ], - ); - - return ( - {children} - ); -}; - -export const useEditorContext = () => { - const context = useContext(EditorContext); - - if (!context) { - throw new Error( - "useEditorContext must be used within an EditorContextProvider", - ); - } - - return context; -}; diff --git a/libs/@hashintel/petrinaut-old/src/petrinaut/examples.ts b/libs/@hashintel/petrinaut-old/src/petrinaut/examples.ts deleted file mode 100644 index 3d7e3183713..00000000000 --- a/libs/@hashintel/petrinaut-old/src/petrinaut/examples.ts +++ /dev/null @@ -1,340 +0,0 @@ -import { nodeDimensions } from "./styling"; -import type { ArcType, NodeType, TokenType } from "./types"; - -export const exampleCPN = { - title: "Drug Production", - tokenTypes: [ - { id: "precursor_a", name: "Precursor A", color: "#3498db" }, - { id: "precursor_b", name: "Precursor B", color: "#f1c40f" }, - { id: "drug", name: "Drug", color: "#2ecc71" }, - { id: "failed_drug", name: "Failed Drug", color: "#e74c3c" }, - ] satisfies TokenType[], - nodes: [ - { - id: "place_0", - type: "place", - position: { x: 20, y: 120 }, - ...nodeDimensions.place, - data: { - type: "place", - label: "Plant A Supply", - initialTokenCounts: { - precursor_a: 10, - precursor_b: 0, - drug: 0, - failed_drug: 0, - }, - }, - }, - { - id: "place_1", - type: "place", - position: { x: 20, y: 600 }, - ...nodeDimensions.place, - data: { - type: "place", - label: "Plant B Supply", - initialTokenCounts: { - precursor_a: 0, - precursor_b: 10, - drug: 0, - failed_drug: 0, - }, - }, - }, - { - id: "place_2", - type: "place", - position: { x: 300, y: 300 }, - ...nodeDimensions.place, - data: { - type: "place", - label: "Manufacturing Plant", - initialTokenCounts: { - precursor_a: 0, - precursor_b: 0, - drug: 0, - failed_drug: 0, - }, - }, - }, - { - id: "place_3", - type: "place", - position: { x: 700, y: 350 }, - ...nodeDimensions.place, - data: { - type: "place", - label: "QA Queue", - initialTokenCounts: { - precursor_a: 0, - precursor_b: 0, - drug: 0, - failed_drug: 0, - }, - }, - }, - { - id: "place_4", - type: "place", - position: { x: 1100, y: 600 }, - ...nodeDimensions.place, - data: { - type: "place", - label: "Disposal", - initialTokenCounts: { - precursor_a: 0, - precursor_b: 0, - drug: 0, - failed_drug: 0, - }, - }, - }, - { - id: "place_5", - type: "place", - position: { x: 1000, y: 200 }, - ...nodeDimensions.place, - data: { - type: "place", - label: "Dispatch", - initialTokenCounts: { - precursor_a: 0, - precursor_b: 0, - drug: 0, - failed_drug: 0, - }, - }, - }, - { - id: "place_6", - type: "place", - position: { x: 1300, y: 380 }, - ...nodeDimensions.place, - data: { - type: "place", - label: "Hospital", - initialTokenCounts: { - precursor_a: 0, - precursor_b: 0, - drug: 0, - failed_drug: 0, - }, - }, - }, - { - id: "transition_0", - type: "transition", - position: { x: 100, y: 400 }, - ...nodeDimensions.transition, - data: { - type: "transition", - label: "Deliver to Plant", - description: "Transport precursors to manufacturing plant", - delay: 2, - }, - }, - { - id: "transition_1", - type: "transition", - position: { x: 490, y: 350 }, - ...nodeDimensions.transition, - data: { - type: "transition", - label: "Manufacture", - description: "Combine precursors to create drug", - delay: 3, - }, - }, - { - id: "transition_2", - type: "transition", - position: { x: 870, y: 400 }, - ...nodeDimensions.transition, - data: { - type: "transition", - label: "Quality Check", - delay: 2, - description: "Quality assurance with conditional outputs", - conditions: [ - { - id: "condition-pass", - name: "Pass", - probability: 80, - outputEdgeId: "transition_2-place_5", - }, - { - id: "condition-fail", - name: "Fail", - probability: 20, - outputEdgeId: "transition_2-place_4", - }, - ], - }, - }, - { - id: "transition_3", - type: "transition", - position: { x: 1150, y: 280 }, - ...nodeDimensions.transition, - data: { - type: "transition", - label: "Ship", - description: "Ship drugs to hospital", - delay: 3, - }, - }, - ] satisfies NodeType[], - arcs: [ - { - id: "place_0-transition_0", - source: "place_0", - target: "transition_0", - type: "default", - interactionWidth: 8, - data: { - tokenWeights: { - precursor_a: 1, - precursor_b: 0, - drug: 0, - failed_drug: 0, - }, - }, - }, - { - id: "place_1-transition_0", - source: "place_1", - target: "transition_0", - type: "default", - interactionWidth: 8, - data: { - tokenWeights: { - precursor_a: 0, - precursor_b: 1, - drug: 0, - failed_drug: 0, - }, - }, - }, - { - id: "transition_0-place_2", - source: "transition_0", - target: "place_2", - type: "default", - interactionWidth: 8, - data: { - tokenWeights: { - precursor_a: 1, - precursor_b: 1, - drug: 0, - failed_drug: 0, - }, - }, - }, - { - id: "place_2-transition_1", - source: "place_2", - target: "transition_1", - type: "default", - interactionWidth: 8, - data: { - tokenWeights: { - precursor_a: 1, - precursor_b: 1, - drug: 0, - failed_drug: 0, - }, - }, - }, - { - id: "transition_1-place_3", - source: "transition_1", - target: "place_3", - type: "default", - interactionWidth: 8, - data: { - tokenWeights: { - precursor_a: 0, - precursor_b: 0, - drug: 1, - failed_drug: 0, - }, - }, - }, - { - id: "place_3-transition_2", - source: "place_3", - target: "transition_2", - type: "default", - interactionWidth: 8, - data: { - tokenWeights: { - precursor_a: 0, - precursor_b: 0, - drug: 1, - failed_drug: 0, - }, - }, - }, - { - id: "transition_2-place_5", - source: "transition_2", - target: "place_5", - type: "default", - interactionWidth: 8, - data: { - tokenWeights: { - precursor_a: 0, - precursor_b: 0, - drug: 1, - failed_drug: 0, - }, - }, - }, - { - id: "transition_2-place_4", - source: "transition_2", - target: "place_4", - type: "default", - interactionWidth: 8, - data: { - tokenWeights: { - precursor_a: 0, - precursor_b: 0, - drug: 0, - failed_drug: 1, - }, - }, - }, - { - id: "place_5-transition_3", - source: "place_5", - target: "transition_3", - type: "default", - interactionWidth: 8, - data: { - tokenWeights: { - precursor_a: 0, - precursor_b: 0, - drug: 1, - failed_drug: 0, - }, - }, - }, - { - id: "transition_3-place_6", - source: "transition_3", - target: "place_6", - type: "default", - interactionWidth: 8, - data: { - tokenWeights: { - precursor_a: 0, - precursor_b: 0, - drug: 1, - failed_drug: 0, - }, - }, - }, - ] satisfies ArcType[], -}; diff --git a/libs/@hashintel/petrinaut-old/src/petrinaut/generate-uuid.ts b/libs/@hashintel/petrinaut-old/src/petrinaut/generate-uuid.ts deleted file mode 100644 index 29c60045acd..00000000000 --- a/libs/@hashintel/petrinaut-old/src/petrinaut/generate-uuid.ts +++ /dev/null @@ -1,3 +0,0 @@ -import { v4 as uuidv4 } from "uuid"; - -export const generateUuid = () => uuidv4(); diff --git a/libs/@hashintel/petrinaut-old/src/petrinaut/log-pane.tsx b/libs/@hashintel/petrinaut-old/src/petrinaut/log-pane.tsx deleted file mode 100644 index d845ea4a2c8..00000000000 --- a/libs/@hashintel/petrinaut-old/src/petrinaut/log-pane.tsx +++ /dev/null @@ -1,104 +0,0 @@ -import { CaretDownSolidIcon, IconButton } from "@hashintel/design-system"; -import { Box, Collapse, Stack, Typography } from "@mui/material"; -import { useEffect, useRef, useState } from "react"; - -import { useSimulationContext } from "./simulation-context"; - -export const LogPane = () => { - const { simulationLogs } = useSimulationContext(); - const logsContainerRef = useRef(null); - const [expanded, setExpanded] = useState(false); - - useEffect(() => { - if (expanded && logsContainerRef.current) { - logsContainerRef.current.scrollTo({ - top: logsContainerRef.current.scrollHeight, - behavior: "smooth", - }); - } - }, [simulationLogs, expanded]); - - if (simulationLogs.length === 0) { - return null; - } - - return ( - - setExpanded(!expanded)} - sx={({ transitions }) => ({ - display: "flex", - justifyContent: "space-between", - p: 0, - pr: 1.5, - alignItems: "center", - "&:hover": { - backgroundColor: "transparent", - "& svg, & span": { color: ({ palette }) => palette.common.black }, - }, - "& svg": { - transform: expanded ? "none" : "rotate(-90deg)", - transition: transitions.create(["transform", "color"], { - duration: 200, - }), - position: "relative", - top: expanded ? -2 : 0, - }, - width: "100%", - })} - > - ({ - color: ({ palette }) => palette.gray[70], - transition: transitions.create("color", { - duration: 200, - }), - })} - > - Simulation Logs - - palette.gray[50], - }} - /> - - - - {simulationLogs.map((log) => ( - - {log.text} - - ))} - - - - ); -}; diff --git a/libs/@hashintel/petrinaut-old/src/petrinaut/net-selector.tsx b/libs/@hashintel/petrinaut-old/src/petrinaut/net-selector.tsx deleted file mode 100644 index 516fefcdf82..00000000000 --- a/libs/@hashintel/petrinaut-old/src/petrinaut/net-selector.tsx +++ /dev/null @@ -1,91 +0,0 @@ -import { Autocomplete, CaretDownSolidIcon } from "@hashintel/design-system"; -import { MenuItem, outlinedInputClasses } from "@mui/material"; -import { useMemo, useRef } from "react"; - -import type { MinimalNetMetadata } from "./types"; - -export const NetSelector = ({ - disabledOptions, - onSelect, - options, - placeholder, - value, -}: { - disabledOptions?: string[]; - onSelect: (option: MinimalNetMetadata) => void; - options: MinimalNetMetadata[]; - placeholder?: string; - value: string | null; -}) => { - const selectedOption = useMemo(() => { - return options.find((option) => option.netId === value); - }, [options, value]); - - const inputRef = useRef(null); - - if (options.length === 0) { - return null; - } - - return ( - - autoFocus={false} - componentsProps={{ - paper: { - sx: { - p: 0, - maxWidth: "90vw", - minWidth: "100%", - width: "fit-content", - }, - }, - }} - disableCloseOnSelect={false} - disabled={options.length === 0} - getOptionDisabled={(option) => - !!option && !!disabledOptions?.includes(option.netId) - } - getOptionLabel={(option) => option?.title ?? ""} - inputHeight={40} - inputProps={{ - endAdornment: , - placeholder: placeholder ?? "Select a net to load", - sx: () => ({ - [`&.${outlinedInputClasses.root}`]: { - py: 0.3, - px: "8px !important", - - input: { - fontSize: 14, - }, - }, - }), - }} - inputRef={inputRef} - isOptionEqualToValue={(option, selectedValue) => - option?.netId === selectedValue?.netId - } - renderOption={(props, data) => ( - - {data?.title ?? ""} - - )} - ListboxProps={{ - sx: { - maxHeight: 240, - }, - }} - onChange={(_event, option) => { - if (!option) { - return; - } - - onSelect(option); - inputRef.current?.blur(); - }} - options={options} - sx={{ maxWidth: 300 }} - value={selectedOption} - /> - ); -}; diff --git a/libs/@hashintel/petrinaut-old/src/petrinaut/place-editor.tsx b/libs/@hashintel/petrinaut-old/src/petrinaut/place-editor.tsx deleted file mode 100644 index 6c91c0b197f..00000000000 --- a/libs/@hashintel/petrinaut-old/src/petrinaut/place-editor.tsx +++ /dev/null @@ -1,128 +0,0 @@ -import { TextField } from "@hashintel/design-system"; -import { Box, Button, Card, Stack, Typography } from "@mui/material"; - -import type { PlaceNodeType, TokenCounts, TokenType } from "./types"; - -export type PlaceEditorProps = { - selectedPlace: PlaceNodeType; - tokenTypes: TokenType[]; - onClose: () => void; - onUpdateInitialTokens: (nodeId: string, tokenCounts: TokenCounts) => void; - onUpdateNodeLabel: (nodeId: string, label: string) => void; - position: { x: number; y: number }; -}; - -export const PlaceEditor = ({ - position, - selectedPlace, - tokenTypes, - onClose, - onUpdateInitialTokens, - onUpdateNodeLabel, -}: PlaceEditorProps) => { - const { data, id: placeId } = selectedPlace; - const { label: nodeName, initialTokenCounts } = data; - - const handleTokenCountChange = (tokenTypeId: string, value: string) => { - const numValue = parseInt(value, 10); - const newCount = Number.isNaN(numValue) ? 0 : Math.max(0, numValue); - - const newTokenCounts = { - ...initialTokenCounts, - [tokenTypeId]: newCount, - }; - - onUpdateInitialTokens(placeId, newTokenCounts); - }; - - const handleNodeNameChange = (event: React.ChangeEvent) => { - const newName = event.target.value; - onUpdateNodeLabel(placeId, newName); - }; - - const handleKeyDown = (event: React.KeyboardEvent) => { - if (event.key === "Enter") { - (event.target as HTMLInputElement).blur(); - } - }; - - return ( - - - - - Name - - - - - {tokenTypes.length > 0 && ( - - - Initial Token Counts - - - {tokenTypes.map((tokenType) => ( - - - - - {tokenType.name} - - - - handleTokenCountChange(tokenType.id, event.target.value) - } - size="small" - sx={{ width: 80 }} - /> - - ))} - - - )} - - - - - - - ); -}; diff --git a/libs/@hashintel/petrinaut-old/src/petrinaut/place-node.tsx b/libs/@hashintel/petrinaut-old/src/petrinaut/place-node.tsx deleted file mode 100644 index 0a3af62eed8..00000000000 --- a/libs/@hashintel/petrinaut-old/src/petrinaut/place-node.tsx +++ /dev/null @@ -1,106 +0,0 @@ -import { IconDiagramRegular } from "@hashintel/design-system"; -import { Box, Tooltip } from "@mui/material"; -import { Handle, type NodeProps, Position } from "reactflow"; - -import { useEditorContext } from "./editor-context"; -import { useSimulationContext } from "./simulation-context"; -import { handleStyling, nodeDimensions, placeStyling } from "./styling"; -import type { PlaceNodeData } from "./types"; - -const tokenSize = 22; -const halfTokenSize = tokenSize / 2; - -const getTokenPosition = (index: number) => { - // Calculate the angle for this token (in radians), starting from top (-π/2), distributed to fit 16 tokens evenly around the place border - const angle = (index * 2 * Math.PI) / 16 - Math.PI / 2; - - const radius = nodeDimensions.place.width / 2; - - return { - left: `calc(50% + ${radius * Math.cos(angle)}px - ${halfTokenSize}px)`, - top: `calc(50% + ${radius * Math.sin(angle)}px - ${halfTokenSize}px + 1px)`, - }; -}; - -export const PlaceNode = ({ - data, - id, - isConnectable, -}: NodeProps) => { - const { placeMarkingsById } = useSimulationContext(); - const { petriNetDefinition } = useEditorContext(); - - const currentTokenCounts = placeMarkingsById[id] ?? {}; - - const nonZeroTokens = Object.entries(currentTokenCounts).filter( - ([_, count]) => count > 0, - ); - - const { parentNetNode } = data; - - return ( - - {parentNetNode && ( - - palette.gray[60], - position: "absolute", - top: 10, - left: nodeDimensions.place.width / 2 - 8, - fontSize: 16, - zIndex: 3, - }} - /> - - )} - - - {data.label} - - {nonZeroTokens.map(([tokenTypeId, count], index) => { - const tokenType = petriNetDefinition.tokenTypes.find( - (tt) => tt.id === tokenTypeId, - ); - - const position = getTokenPosition(index); - - return ( - palette.common.white, - fontSize: 12, - fontWeight: "bold", - padding: "0 4px", - zIndex: 3, - }} - > - {count} - - ); - })} - - - - ); -}; diff --git a/libs/@hashintel/petrinaut-old/src/petrinaut/sidebar.tsx b/libs/@hashintel/petrinaut-old/src/petrinaut/sidebar.tsx deleted file mode 100644 index ecd87e8f709..00000000000 --- a/libs/@hashintel/petrinaut-old/src/petrinaut/sidebar.tsx +++ /dev/null @@ -1,93 +0,0 @@ -import { Box, Button, Stack } from "@mui/material"; -import type { DragEvent } from "react"; -import { useCallback } from "react"; - -import { useEditorContext } from "./editor-context"; -import { placeStyling, transitionStyling } from "./styling"; -import { useLayoutGraph } from "./use-layout-graph"; - -export const Sidebar = () => { - const onDragStart = useCallback( - (event: DragEvent, nodeType: "place" | "transition") => { - event.dataTransfer.setData("application/reactflow", nodeType); - - // eslint-disable-next-line no-param-reassign - event.dataTransfer.effectAllowed = "move"; - }, - [], - ); - - const { petriNetDefinition } = useEditorContext(); - - const layoutGraph = useLayoutGraph(); - - return ( - palette.gray[5], - p: 2, - borderRight: ({ palette }) => `1px solid ${palette.gray[30]}`, - }} - > - - onDragStart(event, "place")} - > - Place - - onDragStart(event, "transition")} - > - Transition - - - palette.gray[30], - height: "1px", - width: "100%", - my: 2, - }} - /> - - - - - ); -}; diff --git a/libs/@hashintel/petrinaut-old/src/petrinaut/simulation-context.tsx b/libs/@hashintel/petrinaut-old/src/petrinaut/simulation-context.tsx deleted file mode 100644 index 4c5ebb50245..00000000000 --- a/libs/@hashintel/petrinaut-old/src/petrinaut/simulation-context.tsx +++ /dev/null @@ -1,341 +0,0 @@ -import { - createContext, - useCallback, - useContext, - useEffect, - useMemo, - useState, -} from "react"; - -import { useEditorContext } from "./editor-context"; -import type { ArcType, PlaceMarkingsById, TransitionNodeType } from "./types"; - -/** - * Check if a transition is enabled, i.e. that the tokens required by each incoming arc are available in the source place. - */ -const checkTransitionEnabled = ({ - transitionId, - arcs, - placeMarkingsById, -}: { - transitionId: string; - arcs: ArcType[]; - placeMarkingsById: PlaceMarkingsById; -}): boolean => { - const incomingArcs = arcs.filter((arc) => arc.target === transitionId); - - return incomingArcs.every((arc) => { - const tokenCounts = placeMarkingsById[arc.source]; - - if (!tokenCounts) { - throw new Error(`Could not find token counts for place ${arc.source}`); - } - - return Object.entries(arc.data?.tokenWeights ?? {}).every( - ([tokenTypeId, requiredWeight]) => { - const availableTokens = tokenCounts[tokenTypeId] ?? 0; - return availableTokens >= (requiredWeight ?? 0); - }, - ); - }); -}; - -type SimulationContextValue = { - currentStep: number; - fireNextStep: () => void; - isSimulating: boolean; - placeMarkingsById: PlaceMarkingsById; - simulationSpeed: number; - simulationLogs: Array<{ id: string; text: string }>; - setSimulationSpeed: (speed: number) => void; - setIsSimulating: (simulating: boolean) => void; - resetSimulation: () => void; -}; - -const SimulationContext = createContext( - undefined, -); - -interface SimulationProviderProps { - children: React.ReactNode; -} - -export const SimulationContextProvider = ({ - children, -}: SimulationProviderProps) => { - const [isSimulating, setIsSimulating] = useState(false); - const [simulationSpeed, setSimulationSpeed] = useState(1000); // ms delay between each step occuring when simulating - const [currentStep, setCurrentStep] = useState(0); - const [simulationLogs, setSimulationLogs] = useState< - Array<{ id: string; text: string }> - >([]); - const [placeMarkingsById, setPlaceMarkingsById] = useState( - {}, - ); - - const { petriNetDefinition } = useEditorContext(); - - const resetMarkings = useCallback(() => { - setPlaceMarkingsById( - petriNetDefinition.nodes.reduce((acc, node) => { - if (node.data.type === "place") { - acc[node.id] = node.data.initialTokenCounts ?? {}; - } - return acc; - }, {} as PlaceMarkingsById), - ); - }, [petriNetDefinition.nodes]); - - const resetSimulation = useCallback(() => { - setIsSimulating(false); - setSimulationLogs([]); - setCurrentStep(0); - resetMarkings(); - }, [resetMarkings]); - - useEffect(() => { - resetSimulation(); - }, [petriNetDefinition.nodes, resetSimulation]); - - const addLogEntry = useCallback( - (message: string) => { - const timestamp = Date.now(); - setSimulationLogs((prevLogs) => { - const newLog = { - id: `log-${timestamp}-${prevLogs.length}`, - text: `[Step ${currentStep}] ${message}`, - }; - return [...prevLogs, newLog]; - }); - }, - [currentStep], - ); - - const fireNextStep = useCallback(() => { - setCurrentStep((prevStep) => prevStep + 1); - - const enabledTransitions = petriNetDefinition.nodes - .filter((node) => node.type === "transition") - .filter((node) => - checkTransitionEnabled({ - transitionId: node.id, - arcs: petriNetDefinition.arcs, - placeMarkingsById, - }), - ); - - if (enabledTransitions.length === 0) { - setIsSimulating(false); - return; - } - - const updatedMarkings = JSON.parse( - JSON.stringify(placeMarkingsById), - ) as PlaceMarkingsById; - - /** - * @todo this logic is wrong, because a transition may be no longer enabled, - * if a transition fires before it and consumes a token it needs. - */ - for (const transition of enabledTransitions) { - const transitionNode = petriNetDefinition.nodes.find( - (node): node is TransitionNodeType => - node.id === transition.id && node.data.type === "transition", - ); - - if (!transitionNode) { - throw new Error(`Transition node ${transition.id} not found`); - } - - let outgoingArc: ArcType | undefined; - if (transitionNode.data.conditions?.length) { - /** - * If the transition has multiple outputs, we need to select one of them randomly based on the probability of each condition. - */ - const randomValue = Math.random() * 100; - let cumulativeProbability = 0; - let selectedCondition = null; - - for (const condition of transitionNode.data.conditions) { - cumulativeProbability += - typeof condition.probability === "number" - ? condition.probability - : 0; - - if (randomValue <= cumulativeProbability) { - selectedCondition = condition; - break; - } - } - - if (!selectedCondition) { - throw new Error("No condition was selected"); - } - - outgoingArc = petriNetDefinition.arcs.find( - (arc) => - arc.source === transition.id && - arc.id === selectedCondition.outputEdgeId, - ); - - addLogEntry( - `Transition "${transitionNode.data.label}" resulted in condition: ${selectedCondition.name} (${selectedCondition.probability}% probability)`, - ); - } else { - /** - * If the transition has only one output, we can just use that. - */ - outgoingArc = petriNetDefinition.arcs.find( - (arc) => arc.source === transition.id, - ); - } - - if (!outgoingArc) { - throw new Error( - `Outgoing edge for transition ${transition.id} not found`, - ); - } - - const incomingArcs = petriNetDefinition.arcs.filter( - (arc) => arc.target === transition.id, - ); - - if (incomingArcs.length === 0) { - throw new Error( - `Expected at least 1 incoming arc for transition ${transition.id}`, - ); - } - - /** - * Update the token counts of each source node based on the requirements of the incoming arc. - */ - for (const incomingArc of incomingArcs) { - for (const [tokenTypeId, weight] of Object.entries( - incomingArc.data?.tokenWeights ?? {}, - )) { - if (!weight) { - continue; - } - - const sourceMarkings = updatedMarkings[incomingArc.source]; - - if (!sourceMarkings) { - throw new Error( - `Could not find token counts for place ${incomingArc.source}`, - ); - } - - sourceMarkings[tokenTypeId] = - (sourceMarkings[tokenTypeId] ?? 0) - weight; - - /** - * This triggers the token animating along the input arc. - * To fully separate simulation logic from the UI, we should instead have the 'step' function be something the UI can call, - * and return the fired transitions from it. - */ - window.dispatchEvent( - new CustomEvent("animateTokenAlongArc", { - detail: { - arcId: incomingArc.id, - tokenTypeId, - }, - }), - ); - } - } - - for (const [tokenTypeId, weight] of Object.entries( - outgoingArc.data?.tokenWeights ?? {}, - )) { - if (!weight) { - continue; - } - - const targetMarkings = updatedMarkings[outgoingArc.target]; - - if (!targetMarkings) { - throw new Error( - `Could not find token counts for place ${outgoingArc.target}`, - ); - } - - targetMarkings[tokenTypeId] = - (targetMarkings[tokenTypeId] ?? 0) + weight; - - /** - * This triggers the token animating along the output arc. - * To fully separate simulation logic from the UI, we should instead have the 'step' function be something the UI can call, - * and return the fired transitions from it. - */ - setTimeout(() => { - window.dispatchEvent( - new CustomEvent("animateTokenAlongArc", { - detail: { - arcId: outgoingArc.id, - tokenTypeId, - }, - }), - ); - /** - * Delay the animation so that it starts after the token animating across the input arc has finished. - */ - }, simulationSpeed / 2); - } - } - - setPlaceMarkingsById(updatedMarkings); - }, [ - petriNetDefinition.arcs, - petriNetDefinition.nodes, - addLogEntry, - simulationSpeed, - placeMarkingsById, - ]); - - useEffect(() => { - if (!isSimulating) { - return undefined; - } - - const interval = setInterval(fireNextStep, simulationSpeed); - - return () => clearInterval(interval); - }, [isSimulating, simulationSpeed, fireNextStep]); - - const contextValue = useMemo( - () => ({ - currentStep, - fireNextStep, - isSimulating, - placeMarkingsById, - resetSimulation, - setIsSimulating, - setSimulationSpeed, - simulationLogs, - simulationSpeed, - }), - [ - currentStep, - fireNextStep, - isSimulating, - placeMarkingsById, - resetSimulation, - simulationLogs, - simulationSpeed, - ], - ); - - return ( - - {children} - - ); -}; - -export const useSimulationContext = (): SimulationContextValue => { - const context = useContext(SimulationContext); - if (!context) { - throw new Error("useSimulation must be used within a SimulationProvider"); - } - return context; -}; diff --git a/libs/@hashintel/petrinaut-old/src/petrinaut/simulation-controls.tsx b/libs/@hashintel/petrinaut-old/src/petrinaut/simulation-controls.tsx deleted file mode 100644 index 788e56dae72..00000000000 --- a/libs/@hashintel/petrinaut-old/src/petrinaut/simulation-controls.tsx +++ /dev/null @@ -1,176 +0,0 @@ -import { - ArrowsRotateRegularIcon, - ForwardStepSolidIcon, - PlaySolidIcon, - Select, - StopSolidIcon, -} from "@hashintel/design-system"; -import { - Box, - Button, - FormControl, - MenuItem, - Stack, - type SvgIconProps, - Tooltip, - Typography, -} from "@mui/material"; -import type { FunctionComponent } from "react"; - -import { useSimulationContext } from "./simulation-context"; - -const SimulationControlButton = ({ - background, - disabled, - Icon, - onClick, - tooltip, -}: { - background: "blue" | "red"; - disabled: boolean; - Icon: FunctionComponent; - onClick: () => void; - tooltip: string; -}) => { - return ( - - - - - - ); -}; - -export const SimulationControls = () => { - const { - isSimulating, - setIsSimulating, - fireNextStep, - resetSimulation, - simulationSpeed, - setSimulationSpeed, - currentStep, - } = useSimulationContext(); - - return ( - - {/* Simulation control buttons */} - - {isSimulating ? ( - setIsSimulating(false)} - tooltip="Stop simulation" - /> - ) : ( - setIsSimulating(true)} - tooltip="Start simulation" - /> - )} - - - - - - {/* Simulation step display */} - - - Step: - - {currentStep} - - - {/* Speed control (the interval between steps) */} - - - - - - - - ); -}; diff --git a/libs/@hashintel/petrinaut-old/src/petrinaut/styling.ts b/libs/@hashintel/petrinaut-old/src/petrinaut/styling.ts deleted file mode 100644 index 771b175bef4..00000000000 --- a/libs/@hashintel/petrinaut-old/src/petrinaut/styling.ts +++ /dev/null @@ -1,53 +0,0 @@ -import { customColors } from "@hashintel/design-system/theme"; -import type { Theme } from "@mui/material"; -import type { SystemStyleObject } from "@mui/system"; - -export const nodeDimensions = { - place: { width: 130, height: 130 }, - transition: { width: 160, height: 80 }, -}; - -export const placeStyling: (theme: Theme) => SystemStyleObject = ({ - palette, -}) => ({ - padding: 1.5, - borderRadius: "50%", - width: nodeDimensions.place.width, - height: nodeDimensions.place.height, - display: "flex", - justifyContent: "center", - alignItems: "center", - background: palette.gray[10], - border: `2px solid ${palette.gray[50]}`, - fontSize: 15, - boxSizing: "border-box", - position: "relative", - textAlign: "center", - lineHeight: 1.3, -}); - -export const transitionStyling: (theme: Theme) => SystemStyleObject = ({ - palette, -}) => ({ - padding: 1.5, - borderRadius: 2, - width: nodeDimensions.transition.width, - height: nodeDimensions.transition.height, - display: "flex", - flexDirection: "column", - justifyContent: "center", - alignItems: "center", - background: palette.gray[20], - border: `2px solid ${palette.gray[50]}`, - fontSize: 15, - boxSizing: "border-box", - position: "relative", -}); - -export const handleStyling = { - background: customColors.gray[60], - width: 10, - height: 10, - borderRadius: "50%", - zIndex: 3, -}; diff --git a/libs/@hashintel/petrinaut-old/src/petrinaut/title-and-net-select.tsx b/libs/@hashintel/petrinaut-old/src/petrinaut/title-and-net-select.tsx deleted file mode 100644 index 802029be7c8..00000000000 --- a/libs/@hashintel/petrinaut-old/src/petrinaut/title-and-net-select.tsx +++ /dev/null @@ -1,80 +0,0 @@ -import { faAngleRight } from "@fortawesome/free-solid-svg-icons"; -import { EditableField } from "@hashintel/block-design-system"; -import { FontAwesomeIcon } from "@hashintel/design-system"; -import { Stack, Typography } from "@mui/material"; - -import { useEditorContext } from "./editor-context"; -import { NetSelector } from "./net-selector"; - -export const TitleAndNetSelect = () => { - const { existingNets, loadPetriNet, parentNet, petriNetId, setTitle, title } = - useEditorContext(); - - return ( - ({ - background: palette.gray[5], - borderBottom: `1px solid ${palette.gray[20]}`, - py: 1, - px: 2, - })} - > - - {parentNet && ( - <> - { - loadPetriNet(parentNet.parentNetId); - }} - sx={({ palette, transitions }) => ({ - background: "none", - border: "none", - color: palette.gray[80], - cursor: "pointer", - transition: transitions.create(["color"], { - duration: 150, - }), - whiteSpace: "nowrap", - "&:hover": { - color: palette.common.black, - }, - })} - > - {parentNet.title} - - ({ - fontSize: 14, - color: palette.gray[50], - mx: 0, - })} - /> - - )} - setTitle(event.target.value)} - placeholder="Process" - /> - - - { - loadPetriNet(net.netId); - }} - options={existingNets.map((net) => ({ - netId: net.netId, - title: net.title, - }))} - value={petriNetId} - /> - - ); -}; diff --git a/libs/@hashintel/petrinaut-old/src/petrinaut/token-types.tsx b/libs/@hashintel/petrinaut-old/src/petrinaut/token-types.tsx deleted file mode 100644 index 77e544d11f2..00000000000 --- a/libs/@hashintel/petrinaut-old/src/petrinaut/token-types.tsx +++ /dev/null @@ -1,62 +0,0 @@ -import { Box, Button, Stack, Typography } from "@mui/material"; -import { useState } from "react"; - -import { useEditorContext } from "./editor-context"; -import { TokenTypeEditor } from "./token-types/token-type-editor"; - -export { defaultTokenTypes } from "./token-types/default-token-types"; - -export const TokenTypes = () => { - const [tokenTypeEditorOpen, setTokenTypeEditorOpen] = useState(false); - - const { petriNetDefinition } = useEditorContext(); - - return ( - <> - setTokenTypeEditorOpen(false)} - /> - - {petriNetDefinition.tokenTypes.map((token) => ( - - - {token.name} - - ))} - - - - - ); -}; diff --git a/libs/@hashintel/petrinaut-old/src/petrinaut/token-types/default-token-types.ts b/libs/@hashintel/petrinaut-old/src/petrinaut/token-types/default-token-types.ts deleted file mode 100644 index 9c35ce2b7a5..00000000000 --- a/libs/@hashintel/petrinaut-old/src/petrinaut/token-types/default-token-types.ts +++ /dev/null @@ -1,5 +0,0 @@ -import type { TokenType } from "../types"; - -export const defaultTokenTypes: TokenType[] = [ - { id: "default", name: "Default", color: "#3498db" }, -]; diff --git a/libs/@hashintel/petrinaut-old/src/petrinaut/token-types/token-type-editor.tsx b/libs/@hashintel/petrinaut-old/src/petrinaut/token-types/token-type-editor.tsx deleted file mode 100644 index 61fda2ba932..00000000000 --- a/libs/@hashintel/petrinaut-old/src/petrinaut/token-types/token-type-editor.tsx +++ /dev/null @@ -1,226 +0,0 @@ -import { TextField } from "@hashintel/design-system"; -import { - Box, - Button, - Dialog, - DialogActions, - DialogContent, - Stack, - Typography, -} from "@mui/material"; -import { useEffect, useState } from "react"; - -import { useEditorContext } from "../editor-context"; -import type { TokenType } from "../types"; - -type TokenTypeEditorProps = { - open: boolean; - onClose: () => void; -}; - -export const TokenTypeEditor = ({ open, onClose }: TokenTypeEditorProps) => { - const { petriNetDefinition, mutatePetriNetDefinition } = useEditorContext(); - - const [localTokenTypes, setLocalTokenTypes] = useState( - petriNetDefinition.tokenTypes, - ); - - useEffect(() => { - setLocalTokenTypes(petriNetDefinition.tokenTypes); - }, [petriNetDefinition.tokenTypes]); - - const [newTokenName, setNewTokenName] = useState(""); - const [newTokenColor, setNewTokenColor] = useState("#3498db"); - - const handleAddToken = () => { - if (newTokenName.trim()) { - const newToken: TokenType = { - id: `token-${Date.now()}`, - name: newTokenName.trim(), - color: newTokenColor, - }; - - setLocalTokenTypes([...localTokenTypes, newToken]); - setNewTokenName(""); - setNewTokenColor("#3498db"); - } - }; - - const handleDeleteToken = (id: string) => { - if (localTokenTypes.length <= 1) { - return; - } - - setLocalTokenTypes(localTokenTypes.filter((token) => token.id !== id)); - }; - - const handleTokenNameChange = (id: string, name: string) => { - setLocalTokenTypes( - localTokenTypes.map((token) => - token.id === id ? { ...token, name } : token, - ), - ); - }; - - /** - * @todo use more granular mutations of the token types - * possibly just get rid of the local state and 'save' button and mutate on every change - */ - const handleSave = () => { - mutatePetriNetDefinition((existingNet) => { - // eslint-disable-next-line no-param-reassign - existingNet.tokenTypes = localTokenTypes; - }); - - onClose(); - }; - - return ( - - - - - - Token Types - - - {localTokenTypes.map((token) => ( - - - - { - setLocalTokenTypes( - localTokenTypes.map((tok) => - tok.id === token.id - ? { ...tok, color: event.currentTarget.value } - : tok, - ), - ); - }} - sx={{ - position: "absolute", - top: 0, - left: 0, - width: "100%", - height: "100%", - opacity: 0, - cursor: "pointer", - }} - /> - - - handleTokenNameChange(token.id, event.target.value) - } - size="small" - sx={{ flex: 1 }} - onClick={(event) => event.stopPropagation()} - /> - - - ))} - - - - - Add New - - - setNewTokenName(event.target.value)} - placeholder="Token name" - size="small" - /> - - - - setNewTokenColor(event.currentTarget.value) - } - sx={{ - position: "absolute", - top: 0, - left: 0, - width: "100%", - height: "100%", - opacity: 0, - cursor: "pointer", - }} - /> - - - - - - - - - - - - - ); -}; diff --git a/libs/@hashintel/petrinaut-old/src/petrinaut/transition-editor.tsx b/libs/@hashintel/petrinaut-old/src/petrinaut/transition-editor.tsx deleted file mode 100644 index 7bf17691e19..00000000000 --- a/libs/@hashintel/petrinaut-old/src/petrinaut/transition-editor.tsx +++ /dev/null @@ -1,795 +0,0 @@ -import { - IconButton, - Select, - TextField, - XMarkRegularIcon, -} from "@hashintel/design-system"; -import { - Box, - Button, - Checkbox, - Dialog, - DialogActions, - DialogContent, - Divider, - FormControlLabel, - MenuItem, - Slider, - Stack, - Switch, - Tooltip, - Typography, -} from "@mui/material"; -import { useCallback, useMemo, useState } from "react"; - -import { useEditorContext } from "./editor-context"; -import { generateUuid } from "./generate-uuid"; -import { NetSelector } from "./net-selector"; -import type { - ArcType, - PlaceNodeType, - TransitionCondition, - TransitionNodeData, - TransitionNodeType, -} from "./types"; - -const normalizeProbabilities = ( - conditions: TransitionCondition[], -): TransitionCondition[] => { - if (conditions.length === 0) { - return []; - } - - if (conditions.length === 1) { - return [ - { - ...conditions[0]!, - probability: 100, - }, - ]; - } - - const totalProbability = conditions.reduce( - (sum, condition) => sum + condition.probability, - 0, - ); - - if (totalProbability === 100) { - return conditions; - } - - const factor = 100 / totalProbability; - const normalizedConditions = conditions.map((condition) => ({ - ...condition, - probability: Math.round(condition.probability * factor), - })); - - // Fix any rounding errors by adjusting the first condition - const newTotal = normalizedConditions.reduce( - (sum, condition) => sum + condition.probability, - 0, - ); - - if (newTotal !== 100 && normalizedConditions.length > 0) { - normalizedConditions[0] = { - ...normalizedConditions[0]!, - probability: normalizedConditions[0]!.probability + (100 - newTotal), - }; - } - - return normalizedConditions; -}; - -const distributeRemovedProbability = ( - conditions: TransitionCondition[], - removedProbability: number, -): TransitionCondition[] => { - if (conditions.length === 0) { - return []; - } - if (conditions.length === 1) { - return normalizeProbabilities(conditions); - } - - const totalRemainingProbability = conditions.reduce( - (sum, condition) => sum + condition.probability, - 0, - ); - - const distributionFactor = - totalRemainingProbability > 0 - ? removedProbability / totalRemainingProbability - : 0; - - return normalizeProbabilities( - conditions.map((condition) => ({ - ...condition, - probability: Math.round(condition.probability * (1 + distributionFactor)), - })), - ); -}; - -const distributeEvenlyWithRemainder = (count: number): number[] => { - const targetProbability = Math.floor(100 / count); - const remainder = 100 - targetProbability * count; - - return Array(count) - .fill(targetProbability) - .map((prob: number, idx) => (idx === 0 ? prob + remainder : prob)); -}; - -type TransitionEditorProps = { - open: boolean; - onClose: () => void; - transitionId: string; - outgoingEdges: Array< - ArcType & { - targetLabel: string; - } - >; - onUpdateTransition: ( - transitionId: string, - data: Omit, - ) => void; -}; - -export const TransitionEditor = ({ - open, - onClose, - transitionId, - outgoingEdges, - onUpdateTransition, -}: TransitionEditorProps) => { - const { petriNetDefinition } = useEditorContext(); - - const { transitionNode, allInputPlaces, allOutputPlaces } = useMemo(() => { - const node = petriNetDefinition.nodes.find( - (option): option is TransitionNodeType => - option.data.type === "transition" && option.id === transitionId, - ); - - if (!node) { - throw new Error("Transition node not found"); - } - - const inputPlaces: PlaceNodeType[] = []; - const outputPlaces: PlaceNodeType[] = []; - - for (const arc of petriNetDefinition.arcs) { - if (arc.source === transitionId) { - const outputPlace = petriNetDefinition.nodes.find( - (option): option is PlaceNodeType => - option.type === "place" && option.id === arc.target, - ); - - if (!outputPlace) { - throw new Error("Output place not found"); - } - - outputPlaces.push(outputPlace); - } - - if (arc.target === transitionId) { - const inputPlace = petriNetDefinition.nodes.find( - (option): option is PlaceNodeType => - option.type === "place" && option.id === arc.source, - ); - - if (!inputPlace) { - throw new Error("Input place not found"); - } - - inputPlaces.push(inputPlace); - } - } - - return { - transitionNode: node, - allInputPlaces: inputPlaces, - allOutputPlaces: outputPlaces, - }; - }, [petriNetDefinition.arcs, petriNetDefinition.nodes, transitionId]); - - const [localData, setEditedData] = useState>( - { - label: transitionNode.data.label, - description: transitionNode.data.description ?? "", - conditions: transitionNode.data.conditions ?? [], - delay: transitionNode.data.delay, - childNet: transitionNode.data.childNet, - }, - ); - - const hasConditions = localData.conditions && localData.conditions.length > 0; - - const { existingNets } = useEditorContext(); - - const updateChildNet = useCallback( - ({ - childNetId, - childNetTitle, - inputPlaceIds, - outputPlaceIds, - }: { - childNetId: string; - childNetTitle: string; - inputPlaceIds: string[]; - outputPlaceIds: string[]; - }) => { - setEditedData((prev) => ({ - ...prev, - childNet: { - childNetId, - childNetTitle, - inputPlaceIds, - outputPlaceIds, - }, - })); - }, - [], - ); - - const handleHasConditionsChange = useCallback( - (event: React.ChangeEvent) => { - setEditedData((prev) => ({ - ...prev, - conditions: event.target.checked - ? [ - { - id: `condition-${Date.now()}`, - name: "Default", - probability: 100, - outputEdgeId: outgoingEdges[0]!.id, - }, - ] - : [], - })); - }, - [outgoingEdges], - ); - - const handleAddCondition = useCallback(() => { - setEditedData((prev) => { - const existingConditions = prev.conditions ?? []; - - if (existingConditions.length === 0) { - const newCondition: TransitionCondition = { - id: `condition-${Date.now()}`, - name: "Condition 1", - probability: 100, - outputEdgeId: outgoingEdges[0]!.id, - }; - - return { - ...prev, - conditions: [newCondition], - }; - } - - const newConditionCount = existingConditions.length + 1; - const probabilities = distributeEvenlyWithRemainder(newConditionCount); - - const adjustedConditions = existingConditions.map((condition, index) => ({ - ...condition, - probability: probabilities[index]!, - })); - - const newCondition: TransitionCondition = { - id: `condition-${generateUuid()}`, - name: `Condition ${newConditionCount}`, - probability: probabilities[newConditionCount - 1]!, - outputEdgeId: outgoingEdges[0]!.id, - }; - - return { - ...prev, - conditions: [...adjustedConditions, newCondition], - }; - }); - }, [outgoingEdges]); - - const handleConditionNameChange = useCallback( - (conditionId: string, name: string) => { - setEditedData((prev) => ({ - ...prev, - conditions: (prev.conditions ?? []).map((condition) => - condition.id === conditionId ? { ...condition, name } : condition, - ), - })); - }, - [], - ); - - const handleRemoveCondition = useCallback((conditionId: string) => { - setEditedData((prev) => { - const conditions = prev.conditions ?? []; - const conditionToRemove = conditions.find( - (condition) => condition.id === conditionId, - ); - - if (!conditionToRemove) { - return prev; - } - - const remainingConditions = conditions.filter( - (condition) => condition.id !== conditionId, - ); - return { - ...prev, - conditions: distributeRemovedProbability( - remainingConditions, - conditionToRemove.probability, - ), - }; - }); - }, []); - - const handleConditionProbabilityChange = useCallback( - (conditionId: string, newProbability: number) => { - setEditedData((prev) => { - const conditions = prev.conditions ?? []; - const updatedConditions = conditions.map((condition) => - condition.id === conditionId - ? { ...condition, probability: newProbability } - : condition, - ); - return { - ...prev, - conditions: normalizeProbabilities(updatedConditions), - }; - }); - }, - [], - ); - - const handleToggleEdgeForCondition = useCallback( - (conditionId: string, edgeId: string) => { - setEditedData((prev) => ({ - ...prev, - conditions: (prev.conditions ?? []).map((condition) => { - if (condition.id === conditionId) { - return { - ...condition, - outputEdgeId: edgeId, - }; - } - return condition; - }), - })); - }, - [], - ); - - const handleSave = useCallback(() => { - const dataToSave = { ...localData }; - - if (dataToSave.conditions && dataToSave.conditions.length > 0) { - dataToSave.conditions = normalizeProbabilities(dataToSave.conditions); - } - - onUpdateTransition(transitionId, dataToSave); - onClose(); - }, [transitionId, localData, onUpdateTransition, onClose]); - - const totalProbability = useMemo(() => { - return (localData.conditions ?? []).reduce( - (sum, condition) => sum + condition.probability, - 0, - ); - }, [localData.conditions]); - - const existingNetsAvailable = existingNets.length > 0; - - return ( - - - - - - Name - - - setEditedData((prev) => ({ - ...prev, - label: event.target.value, - })) - } - fullWidth - size="small" - /> - - - - - Description - - - setEditedData((prev) => ({ - ...prev, - description: event.target.value, - })) - } - fullWidth - size="small" - /> - - - {/* - - Delay (hours) - - - - setEditedData((prev) => ({ - ...prev, - delay: Number.isNaN(event.target.value) - ? undefined - : parseFloat(event.target.value), - })) - } - inputProps={{ min: 0, step: 0.5 }} - sx={{ width: 80 }} - /> - */} - - `1px solid ${palette.gray[20]}`, - borderRadius: 2, - p: 2, - display: existingNetsAvailable ? "block" : "none", - }} - > - {existingNetsAvailable && ( - - - Child net - - - - updateChildNet({ - inputPlaceIds: allInputPlaces.map((place) => place.id), - outputPlaceIds: allOutputPlaces.map((place) => place.id), - ...localData.childNet, - childNetId: value.netId, - childNetTitle: value.title, - }) - } - /> - - )} - - {localData.childNet && ( - <> - - palette.common.black, - }} - > - input places - - - {allInputPlaces.map((place) => { - const isInputPlaceChecked = - !!localData.childNet?.inputPlaceIds.includes(place.id); - const onlyInputPlaceChecked = - localData.childNet?.inputPlaceIds.length === 1 && - isInputPlaceChecked; - - return ( - - { - const isChecked = event.target.checked; - const currentInputPlaceIds = - localData.childNet?.inputPlaceIds ?? []; - const newInputPlaceIds = isChecked - ? [...currentInputPlaceIds, place.id] - : currentInputPlaceIds.filter( - (id) => id !== place.id, - ); - - updateChildNet({ - ...localData.childNet!, - inputPlaceIds: newInputPlaceIds, - outputPlaceIds: - localData.childNet?.outputPlaceIds ?? [], - childNetId: localData.childNet!.childNetId, - childNetTitle: - localData.childNet!.childNetTitle, - }); - }} - sx={{ mr: 0.8 }} - /> - } - label={place.data.label} - sx={{ m: 0 }} - /> - - ); - })} - - - - - palette.common.black, - }} - > - output places - - - {allOutputPlaces.map((place) => { - const isOutputPlaceChecked = - !!localData.childNet?.outputPlaceIds.includes(place.id); - const onlyOutputPlaceChecked = - localData.childNet?.outputPlaceIds.length === 1 && - isOutputPlaceChecked; - - return ( - - { - const isChecked = event.target.checked; - const currentOutputPlaceIds = - localData.childNet?.outputPlaceIds ?? []; - const newOutputPlaceIds = isChecked - ? [...currentOutputPlaceIds, place.id] - : currentOutputPlaceIds.filter( - (id) => id !== place.id, - ); - - updateChildNet({ - ...localData.childNet!, - inputPlaceIds: - localData.childNet?.inputPlaceIds ?? [], - outputPlaceIds: newOutputPlaceIds, - childNetId: localData.childNet!.childNetId, - childNetTitle: - localData.childNet!.childNetTitle, - }); - }} - sx={{ mr: 0.8 }} - /> - } - label={place.data.label} - sx={{ m: 0 }} - /> - - ); - })} - - - - )} - - - - - - - - - This transition has multiple possible outcomes - - - - {hasConditions && ( - - - - totalProbability === 100 - ? palette.green[70] - : palette.red[80], - }} - > - Total Probability: {totalProbability}% - - - - - - {(localData.conditions ?? []).map((condition) => ( - - `1px solid ${palette.gray[20]}`, - borderRadius: 2, - }} - > - - - - - Condition Name - - - handleConditionNameChange( - condition.id, - event.target.value, - ) - } - fullWidth - size="small" - /> - - {(localData.conditions?.length ?? 0) > 1 && ( - - handleRemoveCondition(condition.id) - } - > - - - )} - - - - - Probability: {condition.probability}% - - - handleConditionProbabilityChange( - condition.id, - typeof value === "number" ? value : value[0]!, - ) - } - valueLabelDisplay="auto" - step={1} - min={1} - max={100} - /> - - - - - - Output to - - - {outgoingEdges.length === 0 ? ( - - No output paths available. Add arcs from this - transition first. - - ) : ( - - - - )} - - - - ))} - - - )} - - - - - - - - - ); -}; diff --git a/libs/@hashintel/petrinaut-old/src/petrinaut/transition-node.tsx b/libs/@hashintel/petrinaut-old/src/petrinaut/transition-node.tsx deleted file mode 100644 index 2278c1631b7..00000000000 --- a/libs/@hashintel/petrinaut-old/src/petrinaut/transition-node.tsx +++ /dev/null @@ -1,79 +0,0 @@ -import { IconButton, IconDiagramRegular } from "@hashintel/design-system"; -import { Box, Tooltip, Typography } from "@mui/material"; -import { Handle, type NodeProps, Position } from "reactflow"; - -import { useEditorContext } from "./editor-context"; -import { handleStyling, transitionStyling } from "./styling"; -import type { TransitionNodeData } from "./types"; - -export const TransitionNode = ({ - data, - isConnectable, -}: NodeProps) => { - const { label, description, childNet } = data; - - const { loadPetriNet } = useEditorContext(); - - return ( -
- - - {childNet && ( - - { - event.stopPropagation(); - - loadPetriNet(childNet.childNetId); - }} - sx={{ - position: "absolute", - top: 0, - left: 0, - "&:hover": { - background: "transparent", - "& svg": { - fill: ({ palette }) => palette.blue[70], - }, - }, - }} - > - - - - )} - - {label} - - {description && ( - - {description} - - )} - - -
- ); -}; diff --git a/libs/@hashintel/petrinaut-old/src/petrinaut/types.ts b/libs/@hashintel/petrinaut-old/src/petrinaut/types.ts deleted file mode 100644 index 7b85fa4c24f..00000000000 --- a/libs/@hashintel/petrinaut-old/src/petrinaut/types.ts +++ /dev/null @@ -1,99 +0,0 @@ -import type { Edge, Node } from "reactflow"; - -export type ArcData = { - tokenWeights: { - [tokenTypeId: string]: number | undefined; - }; -}; - -export type ArcType = Omit, "style">; - -export type PlaceNodeData = { - label: string; - initialTokenCounts?: TokenCounts; - /** - * If the net is a child net, it represents the detail of a transition from the parent. - * It must contain at least one each of input and output places to that parent transition. - * This field indicates if this place corresponds to an input or output place to the transition in the parent. - */ - parentNetNode?: { - id: string; - type: "input" | "output"; - }; - type: "place"; -}; - -export type PlaceMarkingsById = Record; - -export type PlaceNodeType = Omit< - Node, - "selected" | "dragging" ->; - -export type TransitionCondition = { - id: string; - name: string; - probability: number; - outputEdgeId: string; -}; - -export type TransitionNodeData = { - conditions?: TransitionCondition[]; - label: string; - delay?: number; - description?: string; - childNet?: { - childNetId: string; - childNetTitle: string; - /** - * The IDs of the input places for this transition which are represented in the childNet (which should appear as starting places there). - * There must be at least one, but not all input places to this transition (in the parent) must appear in the childNet. - */ - inputPlaceIds: string[]; - /** - * The IDs of the output places for this transition which are represented in the childNet (which should appear as ending places there). - * There must be at least one, but not all output places to this transition (in the parent) must appear in the childNet. - */ - outputPlaceIds: string[]; - }; - /** - * Although a reactflow {@link Node} has a 'type' field, the library types don't discriminate on this field in all methods, - * so we add our own discriminating field here to make it easier to narrow between Transition and Place nodes. - */ - type: "transition"; -}; - -export type TransitionNodeType = Omit< - Node, - "selected" | "dragging" ->; - -export type PetriNetDefinitionObject = { - arcs: ArcType[]; - nodes: NodeType[]; - tokenTypes: TokenType[]; -}; - -export type NodeData = PlaceNodeData | TransitionNodeData; - -export type NodeType = Node; - -export type TokenCounts = { - [tokenTypeId: string]: number; -}; - -export type TokenType = { - id: string; - name: string; - color: string; -}; - -export type MinimalNetMetadata = { - netId: string; - title: string; -}; - -export type ParentNet = { - parentNetId: string; - title: string; -}; diff --git a/libs/@hashintel/petrinaut-old/src/petrinaut/use-convert-to-pnml.ts b/libs/@hashintel/petrinaut-old/src/petrinaut/use-convert-to-pnml.ts deleted file mode 100644 index 9a9a071c4f6..00000000000 --- a/libs/@hashintel/petrinaut-old/src/petrinaut/use-convert-to-pnml.ts +++ /dev/null @@ -1,182 +0,0 @@ -import type { - PetriNetDefinitionObject, - PlaceNodeData, - TransitionNodeData, -} from "./types"; - -const escapeXml = (str: string) => - str - .replace(/&/g, "&") - .replace(//g, ">") - .replace(/"/g, """) - .replace(/'/g, "'"); - -/** - * Convert the current process to an ISO-15909-2-conformant HLPN PNML document. - * - * @todo the proper format is to be agreed, this current version is wrong. - */ -export const useConvertToPnml = ({ - petriNet, - title, -}: { - petriNet: PetriNetDefinitionObject; - title: string; -}) => { - const { nodes, arcs, tokenTypes } = petriNet; - - const convertToPnml = (): string => { - /* ---------- Header & namespaces---------- */ - let pnml = ` - - - - ${escapeXml(title)}`; - - /* ---------- HLPN colour-set declarations ---------- */ - pnml += ` - `; - for (const tok of tokenTypes) { - pnml += ` - - - ${escapeXml(tok.name)} - - - - - ${escapeXml(tok.color)} - - `; - } - pnml += ` - `; - - pnml += ` - `; - - /* ---------- Places & initial markings ---------- */ - for (const node of nodes) { - if (node.type !== "place") { - continue; - } - const placeData = node.data as PlaceNodeData; - - pnml += ` - - ${escapeXml(placeData.label)} - - - `; - - for (const [tokId, count] of Object.entries( - placeData.initialTokenCounts ?? {}, - )) { - if (count > 0) { - pnml += ` - - ${escapeXml(tokId)} - `; - } - } - - pnml += ` - - - `; - } - - /* ---------- Transitions ---------- */ - for (const node of nodes) { - if (node.type !== "transition") { - continue; - } - const transitionData = node.data as TransitionNodeData; - - pnml += ` - - ${escapeXml(transitionData.label)} - `; - - if ( - typeof transitionData.delay === "number" || - transitionData.conditions?.length || - transitionData.description - ) { - pnml += ` - `; - - if (transitionData.description) { - pnml += ` - ${escapeXml(transitionData.description)}`; - } - - if (typeof transitionData.delay === "number") { - pnml += ` - - ${transitionData.delay} - `; - } - - if (transitionData.conditions?.length) { - pnml += ` - `; - for (const condition of transitionData.conditions) { - pnml += ` - - ${escapeXml(condition.name)} - `; - } - pnml += ` - `; - } - - pnml += ` - `; - } - - pnml += ` - `; - } - - /* ---------- Arcs & HLPN inscriptions ---------- */ - for (const arc of arcs) { - pnml += ` - - - `; - - for (const [tokId, count] of Object.entries( - arc.data?.tokenWeights ?? {}, - )) { - if (count) { - pnml += ` - - ${escapeXml(tokId)} - `; - } - } - - pnml += ` - - - `; - } - - pnml += ` - - -`; - - return pnml; - }; - - return convertToPnml; -}; diff --git a/libs/@hashintel/petrinaut-old/src/petrinaut/use-layout-graph.ts b/libs/@hashintel/petrinaut-old/src/petrinaut/use-layout-graph.ts deleted file mode 100644 index 2010072c486..00000000000 --- a/libs/@hashintel/petrinaut-old/src/petrinaut/use-layout-graph.ts +++ /dev/null @@ -1,117 +0,0 @@ -import type { ElkNode } from "elkjs"; -import ELK from "elkjs"; -import { useCallback } from "react"; -import { useReactFlow } from "reactflow"; - -import { useEditorContext } from "./editor-context"; -import { nodeDimensions } from "./styling"; -import type { ArcType, NodeType } from "./types"; - -/** - * @see https://eclipse.dev/elk/documentation/tooldevelopers - * @see https://rtsys.informatik.uni-kiel.de/elklive/json.html for JSON playground - */ -const elk = new ELK(); - -const graphPadding = 30; - -/** - * @see https://eclipse.dev/elk/reference.html - */ -const elkLayoutOptions: ElkNode["layoutOptions"] = { - "elk.algorithm": "layered", - "org.eclipse.elk.layered.spacing.nodeNodeBetweenLayers": "100", - "elk.direction": "RIGHT", - "elk.padding": `[left=${graphPadding},top=${ - graphPadding - },right=${graphPadding},bottom=${graphPadding}]`, -}; - -export const useLayoutGraph = () => { - const { fitView } = useReactFlow(); - - const { mutatePetriNetDefinition } = useEditorContext(); - - const layoutGraph = useCallback( - ({ - nodes, - arcs, - animationDuration, - }: { - nodes: NodeType[]; - arcs: ArcType[]; - animationDuration: number; - }) => { - if (nodes.length === 0) { - return; - } - - const graph: ElkNode = { - id: "root", - children: JSON.parse( - JSON.stringify( - nodes.map((node) => ({ - ...node, - /** - * If we are loading a graph in full (e.g. from an example or PNML file), we need to set the visible width and height, - * so that ELK knows how much space to reserve for the node. - * - * @todo encode width/height as part of the graph data - */ - width: nodeDimensions[node.data.type].width, - height: nodeDimensions[node.data.type].height, - })), - ), - ) as ElkNode["children"], - edges: JSON.parse(JSON.stringify(arcs)) as ElkNode["edges"], - layoutOptions: elkLayoutOptions, - }; - - void elk.layout(graph).then((updatedElements) => { - mutatePetriNetDefinition((petriNet) => { - const nodesById = petriNet.nodes.reduce( - (acc, node) => { - acc[node.id] = node; - return acc; - }, - {} as Record, - ); - - /** - * ELK inserts the calculated position as a root 'x' and 'y'. - */ - for (const { x, y, id } of updatedElements.children ?? []) { - const node = nodesById[id]; - - if (!node) { - continue; - } - - if (x && node.position.x !== x) { - node.position.x = x; - } - - if (y && node.position.y !== y) { - node.position.y = y; - } - } - }); - - setTimeout( - () => - window.requestAnimationFrame(() => - fitView({ - duration: animationDuration, - padding: 0.03, - maxZoom: 1, - }), - ), - 300, - ); - }); - }, - [mutatePetriNetDefinition, fitView], - ); - - return layoutGraph; -}; diff --git a/libs/@hashintel/petrinaut-old/src/petrinaut/use-load-from-pnml.ts b/libs/@hashintel/petrinaut-old/src/petrinaut/use-load-from-pnml.ts deleted file mode 100644 index af20288e1a1..00000000000 --- a/libs/@hashintel/petrinaut-old/src/petrinaut/use-load-from-pnml.ts +++ /dev/null @@ -1,195 +0,0 @@ -import { useCallback } from "react"; -import type { Edge, Node } from "reactflow"; - -import type { - ArcData, - ArcType, - NodeType, - PetriNetDefinitionObject, - PlaceNodeData, - TokenCounts, - TokenType, - TransitionCondition, - TransitionNodeData, -} from "./types"; - -const elementToText = (el: Element | null): string => - el?.textContent.trim() ?? ""; - -const getElementsBySelector = (selector: string, parentNode: ParentNode) => - Array.from(parentNode.querySelectorAll(selector)); - -const readMultiset = (multiset: Element | null): TokenCounts => { - const counts: TokenCounts = {}; - - if (!multiset) { - return counts; - } - - for (const el of getElementsBySelector("element", multiset)) { - const tokId = elementToText(el.querySelector("constant")); - const mult = parseInt(el.getAttribute("multiplicity") ?? "1", 10); - counts[tokId] = (counts[tokId] ?? 0) + mult; - } - return counts; -}; - -/** - * Parse a PNML document that follows the HASH “HLPN + toolspecific” dialect back into a {@link PetriNetDefinitionObject}. - * - * @todo this dialect is wrong and needs updating once we have agreed on the format. - */ -const parsePnml = ( - xml: string, -): { - nodes: NodeType[]; - arcs: ArcType[]; - title: string; - tokenTypes: TokenType[]; -} => { - const document = new DOMParser().parseFromString(xml, "application/xml"); - - const titleElement = document.querySelector("pnml > net > name > text"); - const title = elementToText(titleElement); - - const tokenTypes: TokenType[] = getElementsBySelector("colset", document).map( - (colset) => { - const id = colset.getAttribute("id") ?? ""; - const name = elementToText(colset.querySelector("atom")); - const color = elementToText( - colset.querySelector('toolspecific[tool="HASH"] > color'), - ); - return { id, name, color }; - }, - ); - - const placeNodes: Node[] = getElementsBySelector( - "place", - document, - ).map((placeNode) => { - const id = placeNode.getAttribute("id")!; - - const posEl = placeNode.querySelector("graphics > position"); - const position = { - x: parseFloat(posEl?.getAttribute("x") ?? "0"), - y: parseFloat(posEl?.getAttribute("y") ?? "0"), - }; - - const label = elementToText(placeNode.querySelector("name > text")); - - const tokenCounts = readMultiset( - placeNode.querySelector("initialMarking > multiset"), - ); - - const data: PlaceNodeData = { - label, - initialTokenCounts: tokenCounts, - type: "place", - }; - - return { id, type: "place", position, data }; - }); - - const transitionNodes: Node[] = getElementsBySelector( - "transition", - document, - ).map((transitionNode) => { - const id = transitionNode.getAttribute("id")!; - - const posEl = transitionNode.querySelector("graphics > position"); - const position = { - x: parseFloat(posEl?.getAttribute("x") ?? "0"), - y: parseFloat(posEl?.getAttribute("y") ?? "0"), - }; - - const label = elementToText(transitionNode.querySelector("name > text")); - - const toolSpecificContainer = transitionNode.querySelector( - 'toolspecific[tool="HASH"]', - ); - - const description = elementToText( - toolSpecificContainer?.querySelector("description") ?? null, - ); - - const delay = toolSpecificContainer - ? parseFloat( - elementToText(toolSpecificContainer.querySelector("timing > delay")), - ) - : undefined; - - const conditions: TransitionCondition[] = toolSpecificContainer - ? getElementsBySelector("routing > branch", toolSpecificContainer).map( - (b) => ({ - id: b.getAttribute("id") ?? "", - name: elementToText(b.querySelector("name > text")), - probability: parseFloat(b.getAttribute("probability") ?? "0"), - outputEdgeId: b.getAttribute("outputArc") ?? "", - }), - ) - : []; - - const data: TransitionNodeData = { - label, - delay: Number.isNaN(delay) ? undefined : delay, - description, - conditions: conditions.length ? conditions : undefined, - type: "transition", - }; - - return { id, type: "transition", position, data }; - }); - - const arcs: Edge[] = getElementsBySelector("arc", document).map( - (a): ArcType => { - const id = a.getAttribute("id") ?? ""; - const source = a.getAttribute("source") ?? ""; - const target = a.getAttribute("target") ?? ""; - - const tokenWeights = readMultiset( - a.querySelector("inscription > multiset"), - ); - - return { - id, - source, - target, - data: { tokenWeights }, - }; - }, - ); - - return { - nodes: [...placeNodes, ...transitionNodes], - arcs, - tokenTypes, - title, - }; -}; - -export const useLoadFromPnml = ({ - createNewNet, -}: { - createNewNet: (params: { - petriNetDefinition: PetriNetDefinitionObject; - title: string; - }) => void; -}) => { - const load = useCallback( - (xml: string) => { - const { nodes, arcs, tokenTypes, title } = parsePnml(xml); - - createNewNet({ - petriNetDefinition: { - arcs, - nodes, - tokenTypes, - }, - title, - }); - }, - [createNewNet], - ); - - return load; -}; diff --git a/libs/@hashintel/petrinaut-old/theme-override.d.ts b/libs/@hashintel/petrinaut-old/theme-override.d.ts deleted file mode 100644 index c9349a94364..00000000000 --- a/libs/@hashintel/petrinaut-old/theme-override.d.ts +++ /dev/null @@ -1 +0,0 @@ -import "@hashintel/design-system/theme-override"; diff --git a/libs/@hashintel/petrinaut-old/tsconfig.json b/libs/@hashintel/petrinaut-old/tsconfig.json deleted file mode 100644 index a9a3e52ffdb..00000000000 --- a/libs/@hashintel/petrinaut-old/tsconfig.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "extends": "@local/tsconfig/legacy-base-tsconfig-to-refactor.json", - "compilerOptions": { - "jsx": "preserve", - "lib": ["dom", "dom.iterable", "ESNext"], - "noEmit": true - }, - "include": ["theme-override.d.ts", "./src/"], - "exclude": ["./src/petrinaut-sdcpn"] -} diff --git a/libs/@hashintel/petrinaut/assets.d.ts b/libs/@hashintel/petrinaut/assets.d.ts deleted file mode 100644 index cbe652dbe00..00000000000 --- a/libs/@hashintel/petrinaut/assets.d.ts +++ /dev/null @@ -1 +0,0 @@ -declare module "*.css"; diff --git a/libs/@hashintel/petrinaut/eslint.config.js b/libs/@hashintel/petrinaut/eslint.config.js index 2051a37a9de..8a36311150a 100644 --- a/libs/@hashintel/petrinaut/eslint.config.js +++ b/libs/@hashintel/petrinaut/eslint.config.js @@ -7,10 +7,10 @@ export default [ parserOptions: { projectService: { allowDefaultProject: [ - "assets.d.ts", "panda.config.ts", "postcss.config.cjs", "vite.config.ts", + "vite.site.config.ts", ], }, tsconfigRootDir: import.meta.dirname, @@ -43,10 +43,6 @@ export default [ "error", { patterns: [ - { - group: ["@mui/material/*"], - message: "Please import from @mui/material instead", - }, { group: ["@local/*"], message: diff --git a/libs/@hashintel/petrinaut/package.json b/libs/@hashintel/petrinaut/package.json index 9f71018773d..f7d5a4ce172 100644 --- a/libs/@hashintel/petrinaut/package.json +++ b/libs/@hashintel/petrinaut/package.json @@ -8,6 +8,7 @@ "url": "git+https://github.com/hashintel/hash.git", "directory": "libs/@hashintel/petrinaut" }, + "style": "dist/main.css", "files": [ "dist", "CHANGELOG.md", @@ -24,14 +25,13 @@ "main": "dist/main.js", "types": "dist/main.d.ts", "scripts": { - "build": "vite build --mode=lib", - "build:site": "vite build", - "dev": "vite", + "build": "vite build", + "build:site": "vite build --config vite.site.config.ts", + "dev": "vite --config vite.site.config.ts", "fix:eslint": "eslint --fix .", "lint:eslint": "eslint --report-unused-disable-directives .", - "lint:tsc": "tsc --noEmit", + "lint:tsc": "tsgo --noEmit", "prepublishOnly": "turbo run build", - "preview": "vite preview", "test:unit": "vitest" }, "dependencies": { @@ -47,7 +47,6 @@ "@hashintel/refractive": "workspace:^", "@mantine/hooks": "8.3.5", "@monaco-editor/react": "4.8.0-rc.3", - "@mui/material": "5.18.0", "d3-array": "3.2.4", "d3-scale": "4.0.2", "elkjs": "0.11.0", @@ -70,15 +69,15 @@ "@types/d3-scale": "4.0.9", "@types/react": "19.2.7", "@types/react-dom": "19.2.3", - "@vitejs/plugin-react": "5.0.4", + "@typescript/native-preview": "7.0.0-dev.20260216.1", + "@vitejs/plugin-react": "5.1.4", "babel-plugin-react-compiler": "1.0.0", "eslint": "9.39.2", "immer": "10.1.3", "jsdom": "24.1.3", "react": "19.2.3", "react-dom": "19.2.3", - "typescript": "5.9.3", - "vite": "7.1.11", + "vite": "8.0.0-beta.14", "vite-plugin-dts": "4.5.4", "vitest": "4.0.18" }, diff --git a/libs/@hashintel/petrinaut/panda.config.ts b/libs/@hashintel/petrinaut/panda.config.ts index 40f895ecacc..31258ac9396 100644 --- a/libs/@hashintel/petrinaut/panda.config.ts +++ b/libs/@hashintel/petrinaut/panda.config.ts @@ -4,6 +4,21 @@ export default defineConfig({ // Whether to use css reset preflight: { scope: ".petrinaut-root" }, + // Prefix all utility classes (e.g. `.d_flex` → `.pn_d_flex`) + // prefix: "pn", + + // Scope CSS variables to petrinaut root instead of :root + cssVarRoot: ".petrinaut-root", + + // Override light/dark conditions from ds-theme preset so that + // conditional tokens (colors) are scoped to .petrinaut-root instead of :root. + conditions: { + extend: { + light: ".petrinaut-root &", + dark: ".dark .petrinaut-root &, [data-theme='dark'] .petrinaut-root &", + }, + }, + // Where to look for css declarations include: ["./src/**/*.{js,jsx,ts,tsx}"], @@ -26,6 +41,10 @@ export default defineConfig({ }, }, + // Polyfill CSS @layer for embedding in HASH, where unlayered global + // resets (* { padding: 0 }) would otherwise override layered utilities. + polyfill: true, + importMap: "@hashintel/ds-helpers", presets: ["@hashintel/ds-theme"], }); diff --git a/libs/@hashintel/petrinaut/src/components/tooltip.tsx b/libs/@hashintel/petrinaut/src/components/tooltip.tsx index 67bec7e6fe8..325679a3ab8 100644 --- a/libs/@hashintel/petrinaut/src/components/tooltip.tsx +++ b/libs/@hashintel/petrinaut/src/components/tooltip.tsx @@ -1,9 +1,7 @@ import { ark } from "@ark-ui/react/factory"; import { Tooltip as ArkTooltip } from "@ark-ui/react/tooltip"; import { css, cva, cx } from "@hashintel/ds-helpers/css"; -import type { SvgIconProps } from "@mui/material"; -import { SvgIcon, Tooltip as MuiTooltip } from "@mui/material"; -import type { FunctionComponent, ReactNode } from "react"; +import type { ReactNode } from "react"; const tooltipContentStyle = css({ backgroundColor: "neutral.s120", @@ -13,6 +11,12 @@ const tooltipContentStyle = css({ zIndex: "[10000]", boxShadow: "[0 2px 8px rgba(0, 0, 0, 0.15)]", padding: "[6px 10px]", + maxWidth: "[min(300px, var(--available-width))]", + wordWrap: "break-word", + fontWeight: "normal", + textAlign: "left", + textTransform: "none", + letterSpacing: "normal", }); const triggerWrapperStyle = cva({ @@ -71,7 +75,12 @@ export const Tooltip: React.FC = ({ @@ -87,40 +96,33 @@ export const Tooltip: React.FC = ({ ); }; -const CircleInfoIcon: FunctionComponent = (props) => { +const circleInfoIconStyle = css({ + display: "inline-block", + width: "[11px]", + height: "[11px]", + marginLeft: "[6.4px]", + marginBottom: "[1.6px]", + color: "[rgb(160, 160, 160)]", + verticalAlign: "middle", + fill: "[currentColor]", +}); + +const CircleInfoIcon = () => { return ( - + ); }; export const InfoIconTooltip = ({ tooltip }: { tooltip: string }) => { return ( - - - + + + ); }; diff --git a/libs/@hashintel/petrinaut/src/index.css b/libs/@hashintel/petrinaut/src/index.css index 0cd81926fa4..db7789981b3 100644 --- a/libs/@hashintel/petrinaut/src/index.css +++ b/libs/@hashintel/petrinaut/src/index.css @@ -1,5 +1,6 @@ @layer reset, base, tokens, recipes, utilities; .petrinaut-root { + position: relative; background-color: #fafafa; } diff --git a/libs/@hashintel/petrinaut/src/lib/deep-equal.test.ts b/libs/@hashintel/petrinaut/src/lib/deep-equal.test.ts new file mode 100644 index 00000000000..2947827008d --- /dev/null +++ b/libs/@hashintel/petrinaut/src/lib/deep-equal.test.ts @@ -0,0 +1,117 @@ +import { describe, expect, it } from "vitest"; + +import type { SDCPN } from "../core/types/sdcpn"; +import { isSDCPNEqual } from "./deep-equal"; + +const emptyNet: SDCPN = { + places: [], + transitions: [], + types: [], + differentialEquations: [], + parameters: [], +}; + +const sampleNet: SDCPN = { + places: [ + { + id: "p1", + name: "Place 1", + colorId: "c1", + dynamicsEnabled: false, + differentialEquationId: null, + x: 100, + y: 200, + }, + ], + transitions: [ + { + id: "t1", + name: "Transition 1", + inputArcs: [{ placeId: "p1", weight: 1 }], + outputArcs: [{ placeId: "p1", weight: 2 }], + lambdaType: "predicate", + lambdaCode: "return true;", + transitionKernelCode: "return input;", + x: 300, + y: 200, + }, + ], + types: [ + { + id: "c1", + name: "Token", + iconSlug: "circle", + displayColor: "#FF0000", + elements: [{ elementId: "e1", name: "value", type: "real" }], + }, + ], + differentialEquations: [], + parameters: [ + { + id: "param1", + name: "Rate", + variableName: "rate", + type: "real", + defaultValue: "1.0", + }, + ], +}; + +describe("isSDCPNEqual", () => { + it("returns true for two empty nets", () => { + expect(isSDCPNEqual(emptyNet, { ...emptyNet })).toBe(true); + }); + + it("returns true for identical nets", () => { + const copy = JSON.parse(JSON.stringify(sampleNet)) as SDCPN; + expect(isSDCPNEqual(sampleNet, copy)).toBe(true); + }); + + it("returns true for same reference", () => { + expect(isSDCPNEqual(sampleNet, sampleNet)).toBe(true); + }); + + it("returns false when a place differs", () => { + const modified = JSON.parse(JSON.stringify(sampleNet)) as SDCPN; + modified.places[0]!.name = "Renamed"; + expect(isSDCPNEqual(sampleNet, modified)).toBe(false); + }); + + it("returns false when a transition arc weight differs", () => { + const modified = JSON.parse(JSON.stringify(sampleNet)) as SDCPN; + modified.transitions[0]!.outputArcs[0]!.weight = 99; + expect(isSDCPNEqual(sampleNet, modified)).toBe(false); + }); + + it("returns false when a parameter is added", () => { + const modified = JSON.parse(JSON.stringify(sampleNet)) as SDCPN; + modified.parameters.push({ + id: "param2", + name: "Extra", + variableName: "extra", + type: "integer", + defaultValue: "0", + }); + expect(isSDCPNEqual(sampleNet, modified)).toBe(false); + }); + + it("returns false when a color element differs", () => { + const modified = JSON.parse(JSON.stringify(sampleNet)) as SDCPN; + modified.types[0]!.elements[0]!.type = "boolean"; + expect(isSDCPNEqual(sampleNet, modified)).toBe(false); + }); + + it("returns false when places array has different length", () => { + const modified = JSON.parse(JSON.stringify(sampleNet)) as SDCPN; + modified.places.push({ + id: "p2", + name: "Place 2", + colorId: null, + dynamicsEnabled: false, + differentialEquationId: null, + x: 0, + y: 0, + }); + expect(isSDCPNEqual(sampleNet, modified)).toBe(false); + }); +}); diff --git a/libs/@hashintel/petrinaut/src/lib/deep-equal.ts b/libs/@hashintel/petrinaut/src/lib/deep-equal.ts new file mode 100644 index 00000000000..e6a7e871112 --- /dev/null +++ b/libs/@hashintel/petrinaut/src/lib/deep-equal.ts @@ -0,0 +1,74 @@ +import type { SDCPN } from "../core/types/sdcpn"; + +/** + * Recursively compare two values for structural equality. + * + * Handles primitives, arrays, and plain objects. Does not handle + * special types like Date, RegExp, Map, Set, etc. — those are not + * used in SDCPN definitions. + */ +const deepEqual = (a: unknown, b: unknown): boolean => { + // Same reference or identical primitive + if (a === b) { + return true; + } + + // Different types can never be equal + if (typeof a !== typeof b) { + return false; + } + + // One is null but not the other (both-null is caught by `a === b` above) + if (a === null || b === null) { + return false; + } + + // Compare arrays element-by-element + if (Array.isArray(a)) { + if (!Array.isArray(b) || a.length !== b.length) { + return false; + } + for (let i = 0; i < a.length; i++) { + if (!deepEqual(a[i], b[i])) { + return false; + } + } + return true; + } + + // Compare plain objects by own properties + if (typeof a === "object") { + const objA = a as Record; + const objB = b as Record; + + const propsA = Object.getOwnPropertyNames(objA); + const propsB = Object.getOwnPropertyNames(objB); + + // Different number of properties means not equal + if (propsA.length !== propsB.length) { + return false; + } + + // Every property in `a` must exist in `b` with the same value + for (const prop of propsA) { + if ( + !Object.prototype.hasOwnProperty.call(objB, prop) || + !deepEqual(objA[prop], objB[prop]) + ) { + return false; + } + } + + return true; + } + + return false; +}; + +/** + * Check if two SDCPN definitions are structurally identical. + * + * Performs a recursive deep comparison of all fields, so that + * additions to the SDCPN type are automatically covered. + */ +export const isSDCPNEqual = (a: SDCPN, b: SDCPN): boolean => deepEqual(a, b); diff --git a/libs/@hashintel/petrinaut/src/petrinaut.tsx b/libs/@hashintel/petrinaut/src/petrinaut.tsx index f054df4ae0d..c56d600da35 100644 --- a/libs/@hashintel/petrinaut/src/petrinaut.tsx +++ b/libs/@hashintel/petrinaut/src/petrinaut.tsx @@ -20,6 +20,8 @@ import { EditorProvider } from "./state/editor-provider"; import { SDCPNProvider } from "./state/sdcpn-provider"; import { EditorView } from "./views/Editor/editor-view"; +export { isSDCPNEqual } from "./lib/deep-equal"; + export type { Color, DifferentialEquation, diff --git a/libs/@hashintel/petrinaut/src/views/Editor/components/BottomBar/bottom-bar.tsx b/libs/@hashintel/petrinaut/src/views/Editor/components/BottomBar/bottom-bar.tsx index f15b35acd8f..0fca9f54bce 100644 --- a/libs/@hashintel/petrinaut/src/views/Editor/components/BottomBar/bottom-bar.tsx +++ b/libs/@hashintel/petrinaut/src/views/Editor/components/BottomBar/bottom-bar.tsx @@ -40,7 +40,7 @@ const dividerStyle = css({ }); const bottomBarPositionStyle = css({ - position: "fixed", + position: "absolute", left: "[50%]", transform: "translateX(-50%)", zIndex: 1000, diff --git a/libs/@hashintel/petrinaut/src/views/Editor/panels/BottomPanel/panel.tsx b/libs/@hashintel/petrinaut/src/views/Editor/panels/BottomPanel/panel.tsx index 92c62ccb7aa..a87b59dfc11 100644 --- a/libs/@hashintel/petrinaut/src/views/Editor/panels/BottomPanel/panel.tsx +++ b/libs/@hashintel/petrinaut/src/views/Editor/panels/BottomPanel/panel.tsx @@ -141,7 +141,7 @@ export const BottomPanel: React.FC = () => { > = (props) => { const { selectedResourceId, propertiesPanelWidth } = use(EditorContext); const isPropertiesPanelVisible = selectedResourceId !== null; - const rightOffset = isPropertiesPanelVisible - ? propertiesPanelWidth + PANEL_MARGIN * 2 - : PANEL_MARGIN; + const minimapOffset = 12; + const panelOffset = isPropertiesPanelVisible + ? propertiesPanelWidth + PANEL_MARGIN + : 0; return ( > = (props) => { ariaLabel="" className={miniMapClassName} style={{ - top: 0, - right: rightOffset, + top: minimapOffset, + right: minimapOffset + panelOffset, bottom: "auto", left: "auto", width: 130, diff --git a/libs/@hashintel/petrinaut/tsconfig.json b/libs/@hashintel/petrinaut/tsconfig.json index dfc5e99a75e..82a6a739f89 100644 --- a/libs/@hashintel/petrinaut/tsconfig.json +++ b/libs/@hashintel/petrinaut/tsconfig.json @@ -1,11 +1,21 @@ { - "extends": "@local/tsconfig/legacy-base-tsconfig-to-refactor.json", "compilerOptions": { "jsx": "react-jsx", + "target": "es2024", "lib": ["dom", "dom.iterable", "ESNext"], + "module": "preserve", "moduleResolution": "bundler", + "strict": true, + "esModuleInterop": true, + "forceConsistentCasingInFileNames": true, + "noFallthroughCasesInSwitch": true, + "noUncheckedIndexedAccess": true, + "resolveJsonModule": true, "noEmit": true, - "skipLibCheck": true + "skipLibCheck": true, + "declaration": true, + "emitDeclarationOnly": true, + "isolatedModules": true }, "include": ["src", "demo-site"], "exclude": ["demo-site/dist/**/*"] diff --git a/libs/@hashintel/petrinaut/vite.config.ts b/libs/@hashintel/petrinaut/vite.config.ts index a1da6dc726e..744ed2004b9 100644 --- a/libs/@hashintel/petrinaut/vite.config.ts +++ b/libs/@hashintel/petrinaut/vite.config.ts @@ -1,85 +1,98 @@ -import path from "node:path"; - import react from "@vitejs/plugin-react"; -import { defineConfig } from "vite"; +// eslint-disable-next-line import/no-extraneous-dependencies +import { replacePlugin } from "rolldown/plugins"; +import { defineConfig, esmExternalRequirePlugin } from "vite"; import dts from "vite-plugin-dts"; -export default defineConfig(({ mode }) => { - const isLibMode = mode === "lib" || !!process.env.VITEST; - - const environment = process.env.VITE_VERCEL_ENV ?? "development"; - const sentryDsn: string | undefined = process.env.SENTRY_DSN; - - return { - root: isLibMode ? undefined : "demo-site", - - build: isLibMode - ? // Library build - { - lib: { - entry: path.resolve(__dirname, "src/main.ts"), - name: "Petrinaut", - fileName: "main", - formats: ["es"], - }, - rollupOptions: { - external: [ - "@hashintel/ds-components", - "@hashintel/ds-helpers", - "elkjs", - "react", - "react-dom", - "reactflow", - ], - output: { - globals: { - react: "React", - "react-dom": "ReactDOM", - }, - }, - }, - sourcemap: true, - emptyOutDir: true, - minify: false, - } - : // Website build - { - outDir: "dist", +/** + * Library build config + */ +export default defineConfig({ + build: { + lib: { + entry: "src/main.ts", + fileName: "main", + formats: ["es"], + }, + rolldownOptions: { + external: [ + "@hashintel/ds-components", + "@hashintel/ds-helpers", + "react", + "react-dom", + "reactflow", + "typescript", + "monaco-editor", + "@babel/standalone", + ], + output: { + globals: { + react: "React", + "react-dom": "ReactDOM", }, + }, + }, + sourcemap: true, + minify: true, + // Use esbuild for CSS minification. Vite 8 defaults to LightningCSS which + // strips the standard `backdrop-filter` in favour of `-webkit-backdrop-filter` + // based on its browser-target heuristics. + // https://github.com/parcel-bundler/lightningcss/issues/695 + cssMinify: false, + }, - plugins: [ - react({ - babel: { - plugins: ["babel-plugin-react-compiler"], - }, + worker: { + plugins: () => [ + replacePlugin({ + // Consumer Webpack config seem to `define` `typeof window` to `"object"` by default. + // This causes crashes in Web Workers, since `window` is not defined there. + // To prevent this, we do this resolution on our side. + "typeof window": '"undefined"', }), - - isLibMode && - dts({ - rollupTypes: true, - insertTypesEntry: true, - exclude: [ - "**/*.test.*", - "**/*.spec.*", - "playground/**", - "stories/**", - ".storybook/**", - "styled-system/**", - "demo-site/**", - ], - copyDtsFiles: false, - outDir: "dist", - }), ], + }, - define: { - __ENVIRONMENT__: JSON.stringify(environment), - __SENTRY_DSN__: JSON.stringify(sentryDsn), - // Provide minimal process shim for TypeScript language service in browser - "process.versions": JSON.stringify({ pnp: undefined }), - }, - optimizeDeps: { - include: ["@babel/standalone"], + plugins: [ + esmExternalRequirePlugin({ + external: [ + "elkjs", + "react/compiler-runtime", + "react/jsx-runtime", + "react/jsx-dev-runtime", + ], + }), + + react({ + babel: { + plugins: ["babel-plugin-react-compiler"], + }, + }), + + dts({ + rollupTypes: true, + insertTypesEntry: true, + exclude: [ + "**/*.test.*", + "**/*.spec.*", + "playground/**", + "stories/**", + ".storybook/**", + "styled-system/**", + "demo-site/**", + ], + copyDtsFiles: false, + outDir: "dist", + }), + ], + + experimental: { + renderBuiltUrl: (filename) => { + // Fix worker URL for Webpack consumers + // Using `config.base` adds `"" +` prefix to the URL, which breaks the worker URL + if (filename.includes(".worker")) { + return `./${filename}`; + } + return filename; }, - }; + }, }); diff --git a/libs/@hashintel/petrinaut/vite.site.config.ts b/libs/@hashintel/petrinaut/vite.site.config.ts new file mode 100644 index 00000000000..324ba33585e --- /dev/null +++ b/libs/@hashintel/petrinaut/vite.site.config.ts @@ -0,0 +1,28 @@ +import react from "@vitejs/plugin-react"; +import { defineConfig } from "vite"; + +/** Demo site dev server and production build config. */ +export default defineConfig(() => { + const environment = process.env.VITE_VERCEL_ENV ?? "development"; + const sentryDsn: string | undefined = process.env.SENTRY_DSN; + + return { + root: "demo-site", + + define: { + __ENVIRONMENT__: JSON.stringify(environment), + __SENTRY_DSN__: JSON.stringify(sentryDsn), + + // This part could be in the library config + "process.versions": JSON.stringify({ pnp: undefined }), + }, + + plugins: [ + react({ + babel: { + plugins: ["babel-plugin-react-compiler"], + }, + }), + ], + }; +}); diff --git a/yarn.config.cjs b/yarn.config.cjs index 043302f9e3c..6c5e2f9faa4 100644 --- a/yarn.config.cjs +++ b/yarn.config.cjs @@ -17,7 +17,10 @@ const enforcedDevDependencies = { const ignoredDependencies = [ "@sentry/webpack-plugin", - // Petrinaut SDCPN uses multiple packages which are many versions behind in other workspaces + // Petrinaut uses Vite 8 + "vite", + "@vitejs/plugin-react", + // Petrinaut uses multiple packages which are many versions behind in other workspaces // To be un-ignored once H-5639 completed "vitest", "@dnd-kit/sortable", diff --git a/yarn.lock b/yarn.lock index dbde6892e85..43c23acf4c3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -556,7 +556,9 @@ __metadata: "@graphql-codegen/typescript-operations": "npm:2.5.13" "@hashintel/block-design-system": "workspace:*" "@hashintel/design-system": "workspace:*" - "@hashintel/petrinaut-old": "workspace:*" + "@hashintel/ds-components": "workspace:*" + "@hashintel/ds-helpers": "workspace:*" + "@hashintel/petrinaut": "workspace:*" "@hashintel/query-editor": "workspace:*" "@hashintel/type-editor": "workspace:*" "@lit-labs/react": "npm:1.2.1" @@ -3275,14 +3277,14 @@ __metadata: languageName: node linkType: hard -"@babel/code-frame@npm:^7.0.0, @babel/code-frame@npm:^7.10.4, @babel/code-frame@npm:^7.26.0, @babel/code-frame@npm:^7.26.2, @babel/code-frame@npm:^7.27.1, @babel/code-frame@npm:^7.28.6": - version: 7.28.6 - resolution: "@babel/code-frame@npm:7.28.6" +"@babel/code-frame@npm:^7.0.0, @babel/code-frame@npm:^7.10.4, @babel/code-frame@npm:^7.26.0, @babel/code-frame@npm:^7.26.2, @babel/code-frame@npm:^7.27.1, @babel/code-frame@npm:^7.28.6, @babel/code-frame@npm:^7.29.0": + version: 7.29.0 + resolution: "@babel/code-frame@npm:7.29.0" dependencies: "@babel/helper-validator-identifier": "npm:^7.28.5" js-tokens: "npm:^4.0.0" picocolors: "npm:^1.1.1" - checksum: 10c0/ed5d57f99455e3b1c23e75ebb8430c6b9800b4ecd0121b4348b97cecb65406a47778d6db61f0d538a4958bb01b4b277e90348a68d39bd3beff1d7c940ed6dd66 + checksum: 10c0/d34cc504e7765dfb576a663d97067afb614525806b5cad1a5cc1a7183b916fec8ff57fa233585e3926fd5a9e6b31aae6df91aa81ae9775fb7a28f658d3346f0d languageName: node linkType: hard @@ -3339,26 +3341,26 @@ __metadata: languageName: node linkType: hard -"@babel/core@npm:^7.14.0, @babel/core@npm:^7.16.0, @babel/core@npm:^7.18.5, @babel/core@npm:^7.21.3, @babel/core@npm:^7.22.9, @babel/core@npm:^7.23.9, @babel/core@npm:^7.24.4, @babel/core@npm:^7.26.0, @babel/core@npm:^7.28.0, @babel/core@npm:^7.28.4": - version: 7.28.6 - resolution: "@babel/core@npm:7.28.6" +"@babel/core@npm:^7.14.0, @babel/core@npm:^7.16.0, @babel/core@npm:^7.18.5, @babel/core@npm:^7.21.3, @babel/core@npm:^7.22.9, @babel/core@npm:^7.23.9, @babel/core@npm:^7.24.4, @babel/core@npm:^7.26.0, @babel/core@npm:^7.28.0, @babel/core@npm:^7.28.4, @babel/core@npm:^7.29.0": + version: 7.29.0 + resolution: "@babel/core@npm:7.29.0" dependencies: - "@babel/code-frame": "npm:^7.28.6" - "@babel/generator": "npm:^7.28.6" + "@babel/code-frame": "npm:^7.29.0" + "@babel/generator": "npm:^7.29.0" "@babel/helper-compilation-targets": "npm:^7.28.6" "@babel/helper-module-transforms": "npm:^7.28.6" "@babel/helpers": "npm:^7.28.6" - "@babel/parser": "npm:^7.28.6" + "@babel/parser": "npm:^7.29.0" "@babel/template": "npm:^7.28.6" - "@babel/traverse": "npm:^7.28.6" - "@babel/types": "npm:^7.28.6" + "@babel/traverse": "npm:^7.29.0" + "@babel/types": "npm:^7.29.0" "@jridgewell/remapping": "npm:^2.3.5" convert-source-map: "npm:^2.0.0" debug: "npm:^4.1.0" gensync: "npm:^1.0.0-beta.2" json5: "npm:^2.2.3" semver: "npm:^6.3.1" - checksum: 10c0/716b88b1ab057aa53ffa40f2b2fb7e4ab7a35cd6a065fa60e55ca13d2a666672592329f7ea9269aec17e90cc7ce29f42eda566d07859bfd998329a9f283faadb + checksum: 10c0/5127d2e8e842ae409e11bcbb5c2dff9874abf5415e8026925af7308e903f4f43397341467a130490d1a39884f461bc2b67f3063bce0be44340db89687fd852aa languageName: node linkType: hard @@ -3376,16 +3378,16 @@ __metadata: languageName: node linkType: hard -"@babel/generator@npm:^7.14.0, @babel/generator@npm:^7.18.13, @babel/generator@npm:^7.22.9, @babel/generator@npm:^7.26.0, @babel/generator@npm:^7.26.3, @babel/generator@npm:^7.28.3, @babel/generator@npm:^7.28.5, @babel/generator@npm:^7.28.6": - version: 7.28.6 - resolution: "@babel/generator@npm:7.28.6" +"@babel/generator@npm:^7.14.0, @babel/generator@npm:^7.18.13, @babel/generator@npm:^7.22.9, @babel/generator@npm:^7.26.0, @babel/generator@npm:^7.26.3, @babel/generator@npm:^7.28.3, @babel/generator@npm:^7.28.5, @babel/generator@npm:^7.29.0": + version: 7.29.1 + resolution: "@babel/generator@npm:7.29.1" dependencies: - "@babel/parser": "npm:^7.28.6" - "@babel/types": "npm:^7.28.6" + "@babel/parser": "npm:^7.29.0" + "@babel/types": "npm:^7.29.0" "@jridgewell/gen-mapping": "npm:^0.3.12" "@jridgewell/trace-mapping": "npm:^0.3.28" jsesc: "npm:^3.0.2" - checksum: 10c0/162fa358484a9a18e8da1235d998f10ea77c63bab408c8d3e327d5833f120631a77ff022c5ed1d838ee00523f8bb75df1f08196d3657d0bca9f2cfeb8503cc12 + checksum: 10c0/349086e6876258ef3fb2823030fee0f6c0eb9c3ebe35fc572e16997f8c030d765f636ddc6299edae63e760ea6658f8ee9a2edfa6d6b24c9a80c917916b973551 languageName: node linkType: hard @@ -3590,14 +3592,14 @@ __metadata: languageName: node linkType: hard -"@babel/parser@npm:^7.1.0, @babel/parser@npm:^7.14.0, @babel/parser@npm:^7.16.8, @babel/parser@npm:^7.20.7, @babel/parser@npm:^7.23.9, @babel/parser@npm:^7.24.4, @babel/parser@npm:^7.25.4, @babel/parser@npm:^7.25.6, @babel/parser@npm:^7.26.0, @babel/parser@npm:^7.26.3, @babel/parser@npm:^7.26.7, @babel/parser@npm:^7.28.4, @babel/parser@npm:^7.28.5, @babel/parser@npm:^7.28.6": - version: 7.28.6 - resolution: "@babel/parser@npm:7.28.6" +"@babel/parser@npm:^7.1.0, @babel/parser@npm:^7.14.0, @babel/parser@npm:^7.16.8, @babel/parser@npm:^7.20.7, @babel/parser@npm:^7.23.9, @babel/parser@npm:^7.24.4, @babel/parser@npm:^7.25.4, @babel/parser@npm:^7.25.6, @babel/parser@npm:^7.26.0, @babel/parser@npm:^7.26.3, @babel/parser@npm:^7.26.7, @babel/parser@npm:^7.28.4, @babel/parser@npm:^7.28.5, @babel/parser@npm:^7.28.6, @babel/parser@npm:^7.29.0": + version: 7.29.0 + resolution: "@babel/parser@npm:7.29.0" dependencies: - "@babel/types": "npm:^7.28.6" + "@babel/types": "npm:^7.29.0" bin: parser: ./bin/babel-parser.js - checksum: 10c0/d6bfe8aa8e067ef58909e9905496157312372ca65d8d2a4f2b40afbea48d59250163755bba8ae626a615da53d192b084bcfc8c9dad8b01e315b96967600de581 + checksum: 10c0/333b2aa761264b91577a74bee86141ef733f9f9f6d4fc52548e4847dc35dfbf821f58c46832c637bfa761a6d9909d6a68f7d1ed59e17e4ffbb958dc510c17b62 languageName: node linkType: hard @@ -4840,28 +4842,28 @@ __metadata: languageName: node linkType: hard -"@babel/traverse@npm:^7.14.0, @babel/traverse@npm:^7.16.8, @babel/traverse@npm:^7.22.8, @babel/traverse@npm:^7.25.9, @babel/traverse@npm:^7.26.4, @babel/traverse@npm:^7.27.1, @babel/traverse@npm:^7.28.0, @babel/traverse@npm:^7.28.3, @babel/traverse@npm:^7.28.4, @babel/traverse@npm:^7.28.5, @babel/traverse@npm:^7.28.6": - version: 7.28.6 - resolution: "@babel/traverse@npm:7.28.6" +"@babel/traverse@npm:^7.14.0, @babel/traverse@npm:^7.16.8, @babel/traverse@npm:^7.22.8, @babel/traverse@npm:^7.25.9, @babel/traverse@npm:^7.26.4, @babel/traverse@npm:^7.27.1, @babel/traverse@npm:^7.28.0, @babel/traverse@npm:^7.28.3, @babel/traverse@npm:^7.28.4, @babel/traverse@npm:^7.28.5, @babel/traverse@npm:^7.28.6, @babel/traverse@npm:^7.29.0": + version: 7.29.0 + resolution: "@babel/traverse@npm:7.29.0" dependencies: - "@babel/code-frame": "npm:^7.28.6" - "@babel/generator": "npm:^7.28.6" + "@babel/code-frame": "npm:^7.29.0" + "@babel/generator": "npm:^7.29.0" "@babel/helper-globals": "npm:^7.28.0" - "@babel/parser": "npm:^7.28.6" + "@babel/parser": "npm:^7.29.0" "@babel/template": "npm:^7.28.6" - "@babel/types": "npm:^7.28.6" + "@babel/types": "npm:^7.29.0" debug: "npm:^4.3.1" - checksum: 10c0/ed5deb9c3f03e2d1ad2d44b9c92c84cce24593245c3f7871ce27ee1b36d98034e6cd895fa98a94eb44ebabe1d22f51b10b09432939d1c51a0fcaab98f17a97bc + checksum: 10c0/f63ef6e58d02a9fbf3c0e2e5f1c877da3e0bc57f91a19d2223d53e356a76859cbaf51171c9211c71816d94a0e69efa2732fd27ffc0e1bbc84b636e60932333eb languageName: node linkType: hard -"@babel/types@npm:^7.0.0, @babel/types@npm:^7.16.8, @babel/types@npm:^7.18.13, @babel/types@npm:^7.20.7, @babel/types@npm:^7.21.3, @babel/types@npm:^7.22.5, @babel/types@npm:^7.25.4, @babel/types@npm:^7.25.6, @babel/types@npm:^7.26.0, @babel/types@npm:^7.26.3, @babel/types@npm:^7.27.1, @babel/types@npm:^7.27.3, @babel/types@npm:^7.28.2, @babel/types@npm:^7.28.4, @babel/types@npm:^7.28.5, @babel/types@npm:^7.28.6, @babel/types@npm:^7.4.4": - version: 7.28.6 - resolution: "@babel/types@npm:7.28.6" +"@babel/types@npm:^7.0.0, @babel/types@npm:^7.16.8, @babel/types@npm:^7.18.13, @babel/types@npm:^7.20.7, @babel/types@npm:^7.21.3, @babel/types@npm:^7.22.5, @babel/types@npm:^7.25.4, @babel/types@npm:^7.25.6, @babel/types@npm:^7.26.0, @babel/types@npm:^7.26.3, @babel/types@npm:^7.27.1, @babel/types@npm:^7.27.3, @babel/types@npm:^7.28.2, @babel/types@npm:^7.28.4, @babel/types@npm:^7.28.5, @babel/types@npm:^7.28.6, @babel/types@npm:^7.29.0, @babel/types@npm:^7.4.4": + version: 7.29.0 + resolution: "@babel/types@npm:7.29.0" dependencies: "@babel/helper-string-parser": "npm:^7.27.1" "@babel/helper-validator-identifier": "npm:^7.28.5" - checksum: 10c0/54a6a9813e48ef6f35aa73c03b3c1572cad7fa32b61b35dd07e4230bc77b559194519c8a4d8106a041a27cc7a94052579e238a30a32d5509aa4da4d6fd83d990 + checksum: 10c0/23cc3466e83bcbfab8b9bd0edaafdb5d4efdb88b82b3be6728bbade5ba2f0996f84f63b1c5f7a8c0d67efded28300898a5f930b171bb40b311bca2029c4e9b4f languageName: node linkType: hard @@ -7534,15 +7536,6 @@ __metadata: languageName: node linkType: hard -"@handsontable/react@npm:16.1.1": - version: 16.1.1 - resolution: "@handsontable/react@npm:16.1.1" - peerDependencies: - handsontable: ">=16.0.0" - checksum: 10c0/e09d98485896dbecf5af42ae02d63ac8a0540e06b3ba5aab56175f7eb18b938c14663aa87736da2f0d6c7f30fe3a07b57a8f59bc2928d7360d74244ab3c3dd82 - languageName: node - linkType: hard - "@hapi/address@npm:^5.1.1": version: 5.1.1 resolution: "@hapi/address@npm:5.1.1" @@ -7672,7 +7665,7 @@ __metadata: languageName: unknown linkType: soft -"@hashintel/ds-components@workspace:^, @hashintel/ds-components@workspace:libs/@hashintel/ds-components": +"@hashintel/ds-components@workspace:*, @hashintel/ds-components@workspace:^, @hashintel/ds-components@workspace:libs/@hashintel/ds-components": version: 0.0.0-use.local resolution: "@hashintel/ds-components@workspace:libs/@hashintel/ds-components" dependencies: @@ -7768,40 +7761,7 @@ __metadata: languageName: unknown linkType: soft -"@hashintel/petrinaut-old@workspace:*, @hashintel/petrinaut-old@workspace:libs/@hashintel/petrinaut-old": - version: 0.0.0-use.local - resolution: "@hashintel/petrinaut-old@workspace:libs/@hashintel/petrinaut-old" - dependencies: - "@emotion/react": "npm:11.14.0" - "@fortawesome/free-solid-svg-icons": "npm:6.7.2" - "@handsontable/react": "npm:16.1.1" - "@hashintel/block-design-system": "workspace:*" - "@hashintel/design-system": "workspace:*" - "@local/eslint": "workspace:*" - "@mantine/hooks": "npm:8.3.5" - "@mui/material": "npm:5.18.0" - "@mui/system": "npm:5.18.0" - "@types/react": "npm:19.2.7" - "@types/react-dom": "npm:19.2.3" - "@vitejs/plugin-react": "npm:5.0.4" - elkjs: "npm:0.11.0" - eslint: "npm:9.39.2" - immer: "npm:10.1.3" - react: "npm:19.2.3" - react-dom: "npm:19.2.3" - reactflow: "npm:11.11.4" - typescript: "npm:5.9.3" - uuid: "npm:13.0.0" - vite: "npm:7.1.11" - web-worker: "npm:1.4.1" - zustand: "npm:5.0.8" - peerDependencies: - react: ^19.0.0 - react-dom: ^19.0.0 - languageName: unknown - linkType: soft - -"@hashintel/petrinaut@workspace:libs/@hashintel/petrinaut": +"@hashintel/petrinaut@workspace:*, @hashintel/petrinaut@workspace:libs/@hashintel/petrinaut": version: 0.0.0-use.local resolution: "@hashintel/petrinaut@workspace:libs/@hashintel/petrinaut" dependencies: @@ -7818,7 +7778,6 @@ __metadata: "@local/eslint": "workspace:*" "@mantine/hooks": "npm:8.3.5" "@monaco-editor/react": "npm:4.8.0-rc.3" - "@mui/material": "npm:5.18.0" "@pandacss/dev": "npm:1.4.3" "@sentry/react": "npm:10.22.0" "@testing-library/dom": "npm:10.4.1" @@ -7828,7 +7787,8 @@ __metadata: "@types/d3-scale": "npm:4.0.9" "@types/react": "npm:19.2.7" "@types/react-dom": "npm:19.2.3" - "@vitejs/plugin-react": "npm:5.0.4" + "@typescript/native-preview": "npm:7.0.0-dev.20260216.1" + "@vitejs/plugin-react": "npm:5.1.4" babel-plugin-react-compiler: "npm:1.0.0" d3-array: "npm:3.2.4" d3-scale: "npm:4.0.2" @@ -7843,7 +7803,7 @@ __metadata: reactflow: "npm:11.11.4" typescript: "npm:5.9.3" uuid: "npm:13.0.0" - vite: "npm:7.1.11" + vite: "npm:8.0.0-beta.14" vite-plugin-dts: "npm:4.5.4" vitest: "npm:4.0.18" web-worker: "npm:1.4.1" @@ -11717,6 +11677,13 @@ __metadata: languageName: node linkType: hard +"@oxc-project/runtime@npm:0.113.0": + version: 0.113.0 + resolution: "@oxc-project/runtime@npm:0.113.0" + checksum: 10c0/3a9a3f595986812c83ef5dcb29fa55fc0c8e0e873ab99d95c381b7e54b88f57030cd954a87a24b980dfb9eff5bc2f66426aa86ebdcd7b4dd9773be621517d5a0 + languageName: node + linkType: hard + "@oxc-project/types@npm:=0.103.0": version: 0.103.0 resolution: "@oxc-project/types@npm:0.103.0" @@ -11731,6 +11698,13 @@ __metadata: languageName: node linkType: hard +"@oxc-project/types@npm:=0.113.0": + version: 0.113.0 + resolution: "@oxc-project/types@npm:0.113.0" + checksum: 10c0/a5172494732d6f79eaa73246065e0c5ecfff94900191b930a6bc28f0eaced91b93276a971fdf088db902775ed3d3ea7dca9f45fa2795b69ec8ccc0c35dacaa0d + languageName: node + linkType: hard + "@pandacss/config@npm:1.4.3, @pandacss/config@npm:^1.4.3": version: 1.4.3 resolution: "@pandacss/config@npm:1.4.3" @@ -13808,6 +13782,13 @@ __metadata: languageName: node linkType: hard +"@rolldown/binding-android-arm64@npm:1.0.0-rc.4": + version: 1.0.0-rc.4 + resolution: "@rolldown/binding-android-arm64@npm:1.0.0-rc.4" + conditions: os=android & cpu=arm64 + languageName: node + linkType: hard + "@rolldown/binding-darwin-arm64@npm:1.0.0-beta.57": version: 1.0.0-beta.57 resolution: "@rolldown/binding-darwin-arm64@npm:1.0.0-beta.57" @@ -13822,6 +13803,13 @@ __metadata: languageName: node linkType: hard +"@rolldown/binding-darwin-arm64@npm:1.0.0-rc.4": + version: 1.0.0-rc.4 + resolution: "@rolldown/binding-darwin-arm64@npm:1.0.0-rc.4" + conditions: os=darwin & cpu=arm64 + languageName: node + linkType: hard + "@rolldown/binding-darwin-x64@npm:1.0.0-beta.57": version: 1.0.0-beta.57 resolution: "@rolldown/binding-darwin-x64@npm:1.0.0-beta.57" @@ -13836,6 +13824,13 @@ __metadata: languageName: node linkType: hard +"@rolldown/binding-darwin-x64@npm:1.0.0-rc.4": + version: 1.0.0-rc.4 + resolution: "@rolldown/binding-darwin-x64@npm:1.0.0-rc.4" + conditions: os=darwin & cpu=x64 + languageName: node + linkType: hard + "@rolldown/binding-freebsd-x64@npm:1.0.0-beta.57": version: 1.0.0-beta.57 resolution: "@rolldown/binding-freebsd-x64@npm:1.0.0-beta.57" @@ -13850,6 +13845,13 @@ __metadata: languageName: node linkType: hard +"@rolldown/binding-freebsd-x64@npm:1.0.0-rc.4": + version: 1.0.0-rc.4 + resolution: "@rolldown/binding-freebsd-x64@npm:1.0.0-rc.4" + conditions: os=freebsd & cpu=x64 + languageName: node + linkType: hard + "@rolldown/binding-linux-arm-gnueabihf@npm:1.0.0-beta.57": version: 1.0.0-beta.57 resolution: "@rolldown/binding-linux-arm-gnueabihf@npm:1.0.0-beta.57" @@ -13864,6 +13866,13 @@ __metadata: languageName: node linkType: hard +"@rolldown/binding-linux-arm-gnueabihf@npm:1.0.0-rc.4": + version: 1.0.0-rc.4 + resolution: "@rolldown/binding-linux-arm-gnueabihf@npm:1.0.0-rc.4" + conditions: os=linux & cpu=arm + languageName: node + linkType: hard + "@rolldown/binding-linux-arm64-gnu@npm:1.0.0-beta.57": version: 1.0.0-beta.57 resolution: "@rolldown/binding-linux-arm64-gnu@npm:1.0.0-beta.57" @@ -13878,6 +13887,13 @@ __metadata: languageName: node linkType: hard +"@rolldown/binding-linux-arm64-gnu@npm:1.0.0-rc.4": + version: 1.0.0-rc.4 + resolution: "@rolldown/binding-linux-arm64-gnu@npm:1.0.0-rc.4" + conditions: os=linux & cpu=arm64 & libc=glibc + languageName: node + linkType: hard + "@rolldown/binding-linux-arm64-musl@npm:1.0.0-beta.57": version: 1.0.0-beta.57 resolution: "@rolldown/binding-linux-arm64-musl@npm:1.0.0-beta.57" @@ -13892,6 +13908,13 @@ __metadata: languageName: node linkType: hard +"@rolldown/binding-linux-arm64-musl@npm:1.0.0-rc.4": + version: 1.0.0-rc.4 + resolution: "@rolldown/binding-linux-arm64-musl@npm:1.0.0-rc.4" + conditions: os=linux & cpu=arm64 & libc=musl + languageName: node + linkType: hard + "@rolldown/binding-linux-x64-gnu@npm:1.0.0-beta.57": version: 1.0.0-beta.57 resolution: "@rolldown/binding-linux-x64-gnu@npm:1.0.0-beta.57" @@ -13906,6 +13929,13 @@ __metadata: languageName: node linkType: hard +"@rolldown/binding-linux-x64-gnu@npm:1.0.0-rc.4": + version: 1.0.0-rc.4 + resolution: "@rolldown/binding-linux-x64-gnu@npm:1.0.0-rc.4" + conditions: os=linux & cpu=x64 & libc=glibc + languageName: node + linkType: hard + "@rolldown/binding-linux-x64-musl@npm:1.0.0-beta.57": version: 1.0.0-beta.57 resolution: "@rolldown/binding-linux-x64-musl@npm:1.0.0-beta.57" @@ -13920,6 +13950,13 @@ __metadata: languageName: node linkType: hard +"@rolldown/binding-linux-x64-musl@npm:1.0.0-rc.4": + version: 1.0.0-rc.4 + resolution: "@rolldown/binding-linux-x64-musl@npm:1.0.0-rc.4" + conditions: os=linux & cpu=x64 & libc=musl + languageName: node + linkType: hard + "@rolldown/binding-openharmony-arm64@npm:1.0.0-beta.57": version: 1.0.0-beta.57 resolution: "@rolldown/binding-openharmony-arm64@npm:1.0.0-beta.57" @@ -13934,6 +13971,13 @@ __metadata: languageName: node linkType: hard +"@rolldown/binding-openharmony-arm64@npm:1.0.0-rc.4": + version: 1.0.0-rc.4 + resolution: "@rolldown/binding-openharmony-arm64@npm:1.0.0-rc.4" + conditions: os=openharmony & cpu=arm64 + languageName: node + linkType: hard + "@rolldown/binding-wasm32-wasi@npm:1.0.0-beta.57": version: 1.0.0-beta.57 resolution: "@rolldown/binding-wasm32-wasi@npm:1.0.0-beta.57" @@ -13952,6 +13996,15 @@ __metadata: languageName: node linkType: hard +"@rolldown/binding-wasm32-wasi@npm:1.0.0-rc.4": + version: 1.0.0-rc.4 + resolution: "@rolldown/binding-wasm32-wasi@npm:1.0.0-rc.4" + dependencies: + "@napi-rs/wasm-runtime": "npm:^1.1.1" + conditions: cpu=wasm32 + languageName: node + linkType: hard + "@rolldown/binding-win32-arm64-msvc@npm:1.0.0-beta.57": version: 1.0.0-beta.57 resolution: "@rolldown/binding-win32-arm64-msvc@npm:1.0.0-beta.57" @@ -13966,6 +14019,13 @@ __metadata: languageName: node linkType: hard +"@rolldown/binding-win32-arm64-msvc@npm:1.0.0-rc.4": + version: 1.0.0-rc.4 + resolution: "@rolldown/binding-win32-arm64-msvc@npm:1.0.0-rc.4" + conditions: os=win32 & cpu=arm64 + languageName: node + linkType: hard + "@rolldown/binding-win32-x64-msvc@npm:1.0.0-beta.57": version: 1.0.0-beta.57 resolution: "@rolldown/binding-win32-x64-msvc@npm:1.0.0-beta.57" @@ -13980,6 +14040,13 @@ __metadata: languageName: node linkType: hard +"@rolldown/binding-win32-x64-msvc@npm:1.0.0-rc.4": + version: 1.0.0-rc.4 + resolution: "@rolldown/binding-win32-x64-msvc@npm:1.0.0-rc.4" + conditions: os=win32 & cpu=x64 + languageName: node + linkType: hard + "@rolldown/pluginutils@npm:1.0.0-beta.27": version: 1.0.0-beta.27 resolution: "@rolldown/pluginutils@npm:1.0.0-beta.27" @@ -14008,6 +14075,20 @@ __metadata: languageName: node linkType: hard +"@rolldown/pluginutils@npm:1.0.0-rc.3": + version: 1.0.0-rc.3 + resolution: "@rolldown/pluginutils@npm:1.0.0-rc.3" + checksum: 10c0/3928b6282a30f307d1b075d2f217180ae173ea9e00638ce46ab65f089bd5f7a0b2c488ae1ce530f509387793c656a2910337c4cd68fa9d37d7e439365989e699 + languageName: node + linkType: hard + +"@rolldown/pluginutils@npm:1.0.0-rc.4": + version: 1.0.0-rc.4 + resolution: "@rolldown/pluginutils@npm:1.0.0-rc.4" + checksum: 10c0/c3a4e8c3a4cda43294d04fbaa5066476b933c75b0b5dbee66aaa4ea9bbcf34675dbddd53ffaaf6160b244f1a868a42282aec85a00942fd5a166652f4e2fca192 + languageName: node + linkType: hard + "@rollup/plugin-commonjs@npm:28.0.1": version: 28.0.1 resolution: "@rollup/plugin-commonjs@npm:28.0.1" @@ -19041,6 +19122,87 @@ __metadata: languageName: node linkType: hard +"@typescript/native-preview-darwin-arm64@npm:7.0.0-dev.20260216.1": + version: 7.0.0-dev.20260216.1 + resolution: "@typescript/native-preview-darwin-arm64@npm:7.0.0-dev.20260216.1" + conditions: os=darwin & cpu=arm64 + languageName: node + linkType: hard + +"@typescript/native-preview-darwin-x64@npm:7.0.0-dev.20260216.1": + version: 7.0.0-dev.20260216.1 + resolution: "@typescript/native-preview-darwin-x64@npm:7.0.0-dev.20260216.1" + conditions: os=darwin & cpu=x64 + languageName: node + linkType: hard + +"@typescript/native-preview-linux-arm64@npm:7.0.0-dev.20260216.1": + version: 7.0.0-dev.20260216.1 + resolution: "@typescript/native-preview-linux-arm64@npm:7.0.0-dev.20260216.1" + conditions: os=linux & cpu=arm64 + languageName: node + linkType: hard + +"@typescript/native-preview-linux-arm@npm:7.0.0-dev.20260216.1": + version: 7.0.0-dev.20260216.1 + resolution: "@typescript/native-preview-linux-arm@npm:7.0.0-dev.20260216.1" + conditions: os=linux & cpu=arm + languageName: node + linkType: hard + +"@typescript/native-preview-linux-x64@npm:7.0.0-dev.20260216.1": + version: 7.0.0-dev.20260216.1 + resolution: "@typescript/native-preview-linux-x64@npm:7.0.0-dev.20260216.1" + conditions: os=linux & cpu=x64 + languageName: node + linkType: hard + +"@typescript/native-preview-win32-arm64@npm:7.0.0-dev.20260216.1": + version: 7.0.0-dev.20260216.1 + resolution: "@typescript/native-preview-win32-arm64@npm:7.0.0-dev.20260216.1" + conditions: os=win32 & cpu=arm64 + languageName: node + linkType: hard + +"@typescript/native-preview-win32-x64@npm:7.0.0-dev.20260216.1": + version: 7.0.0-dev.20260216.1 + resolution: "@typescript/native-preview-win32-x64@npm:7.0.0-dev.20260216.1" + conditions: os=win32 & cpu=x64 + languageName: node + linkType: hard + +"@typescript/native-preview@npm:7.0.0-dev.20260216.1": + version: 7.0.0-dev.20260216.1 + resolution: "@typescript/native-preview@npm:7.0.0-dev.20260216.1" + dependencies: + "@typescript/native-preview-darwin-arm64": "npm:7.0.0-dev.20260216.1" + "@typescript/native-preview-darwin-x64": "npm:7.0.0-dev.20260216.1" + "@typescript/native-preview-linux-arm": "npm:7.0.0-dev.20260216.1" + "@typescript/native-preview-linux-arm64": "npm:7.0.0-dev.20260216.1" + "@typescript/native-preview-linux-x64": "npm:7.0.0-dev.20260216.1" + "@typescript/native-preview-win32-arm64": "npm:7.0.0-dev.20260216.1" + "@typescript/native-preview-win32-x64": "npm:7.0.0-dev.20260216.1" + dependenciesMeta: + "@typescript/native-preview-darwin-arm64": + optional: true + "@typescript/native-preview-darwin-x64": + optional: true + "@typescript/native-preview-linux-arm": + optional: true + "@typescript/native-preview-linux-arm64": + optional: true + "@typescript/native-preview-linux-x64": + optional: true + "@typescript/native-preview-win32-arm64": + optional: true + "@typescript/native-preview-win32-x64": + optional: true + bin: + tsgo: bin/tsgo.js + checksum: 10c0/db461ea3c92cf34f865e37418498aa6af38375af71d747550121468e310f8a1cc3e74467a61c240ad55b33d5c4728dc2be8eb3a1b2ee718d8c783af710df3c68 + languageName: node + linkType: hard + "@typespec/ts-http-runtime@npm:^0.3.0": version: 0.3.1 resolution: "@typespec/ts-http-runtime@npm:0.3.1" @@ -19265,6 +19427,22 @@ __metadata: languageName: node linkType: hard +"@vitejs/plugin-react@npm:5.1.4": + version: 5.1.4 + resolution: "@vitejs/plugin-react@npm:5.1.4" + dependencies: + "@babel/core": "npm:^7.29.0" + "@babel/plugin-transform-react-jsx-self": "npm:^7.27.1" + "@babel/plugin-transform-react-jsx-source": "npm:^7.27.1" + "@rolldown/pluginutils": "npm:1.0.0-rc.3" + "@types/babel__core": "npm:^7.20.5" + react-refresh: "npm:^0.18.0" + peerDependencies: + vite: ^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 + checksum: 10c0/dd7b8f40717ecd4a5ab18f467134ea8135f9a443359333d71e4114aeacfc8b679be9fd36dc12290d076c78883a02e708bfe1f0d93411c06c9659da0879b952e3 + languageName: node + linkType: hard + "@vitejs/plugin-react@npm:^4.3.4": version: 4.7.0 resolution: "@vitejs/plugin-react@npm:4.7.0" @@ -25196,7 +25374,7 @@ __metadata: languageName: node linkType: hard -"detect-libc@npm:^2.0.0, detect-libc@npm:^2.0.1, detect-libc@npm:^2.0.4": +"detect-libc@npm:^2.0.0, detect-libc@npm:^2.0.1, detect-libc@npm:^2.0.3, detect-libc@npm:^2.0.4": version: 2.0.4 resolution: "detect-libc@npm:2.0.4" checksum: 10c0/c15541f836eba4b1f521e4eecc28eefefdbc10a94d3b8cb4c507689f332cc111babb95deda66f2de050b22122113189986d5190be97d51b5a2b23b938415e67c @@ -32998,6 +33176,13 @@ __metadata: languageName: node linkType: hard +"lightningcss-android-arm64@npm:1.31.1": + version: 1.31.1 + resolution: "lightningcss-android-arm64@npm:1.31.1" + conditions: os=android & cpu=arm64 + languageName: node + linkType: hard + "lightningcss-darwin-arm64@npm:1.25.1": version: 1.25.1 resolution: "lightningcss-darwin-arm64@npm:1.25.1" @@ -33005,6 +33190,13 @@ __metadata: languageName: node linkType: hard +"lightningcss-darwin-arm64@npm:1.31.1": + version: 1.31.1 + resolution: "lightningcss-darwin-arm64@npm:1.31.1" + conditions: os=darwin & cpu=arm64 + languageName: node + linkType: hard + "lightningcss-darwin-x64@npm:1.25.1": version: 1.25.1 resolution: "lightningcss-darwin-x64@npm:1.25.1" @@ -33012,6 +33204,13 @@ __metadata: languageName: node linkType: hard +"lightningcss-darwin-x64@npm:1.31.1": + version: 1.31.1 + resolution: "lightningcss-darwin-x64@npm:1.31.1" + conditions: os=darwin & cpu=x64 + languageName: node + linkType: hard + "lightningcss-freebsd-x64@npm:1.25.1": version: 1.25.1 resolution: "lightningcss-freebsd-x64@npm:1.25.1" @@ -33019,6 +33218,13 @@ __metadata: languageName: node linkType: hard +"lightningcss-freebsd-x64@npm:1.31.1": + version: 1.31.1 + resolution: "lightningcss-freebsd-x64@npm:1.31.1" + conditions: os=freebsd & cpu=x64 + languageName: node + linkType: hard + "lightningcss-linux-arm-gnueabihf@npm:1.25.1": version: 1.25.1 resolution: "lightningcss-linux-arm-gnueabihf@npm:1.25.1" @@ -33026,6 +33232,13 @@ __metadata: languageName: node linkType: hard +"lightningcss-linux-arm-gnueabihf@npm:1.31.1": + version: 1.31.1 + resolution: "lightningcss-linux-arm-gnueabihf@npm:1.31.1" + conditions: os=linux & cpu=arm + languageName: node + linkType: hard + "lightningcss-linux-arm64-gnu@npm:1.25.1": version: 1.25.1 resolution: "lightningcss-linux-arm64-gnu@npm:1.25.1" @@ -33033,6 +33246,13 @@ __metadata: languageName: node linkType: hard +"lightningcss-linux-arm64-gnu@npm:1.31.1": + version: 1.31.1 + resolution: "lightningcss-linux-arm64-gnu@npm:1.31.1" + conditions: os=linux & cpu=arm64 & libc=glibc + languageName: node + linkType: hard + "lightningcss-linux-arm64-musl@npm:1.25.1": version: 1.25.1 resolution: "lightningcss-linux-arm64-musl@npm:1.25.1" @@ -33040,6 +33260,13 @@ __metadata: languageName: node linkType: hard +"lightningcss-linux-arm64-musl@npm:1.31.1": + version: 1.31.1 + resolution: "lightningcss-linux-arm64-musl@npm:1.31.1" + conditions: os=linux & cpu=arm64 & libc=musl + languageName: node + linkType: hard + "lightningcss-linux-x64-gnu@npm:1.25.1": version: 1.25.1 resolution: "lightningcss-linux-x64-gnu@npm:1.25.1" @@ -33047,6 +33274,13 @@ __metadata: languageName: node linkType: hard +"lightningcss-linux-x64-gnu@npm:1.31.1": + version: 1.31.1 + resolution: "lightningcss-linux-x64-gnu@npm:1.31.1" + conditions: os=linux & cpu=x64 & libc=glibc + languageName: node + linkType: hard + "lightningcss-linux-x64-musl@npm:1.25.1": version: 1.25.1 resolution: "lightningcss-linux-x64-musl@npm:1.25.1" @@ -33054,6 +33288,20 @@ __metadata: languageName: node linkType: hard +"lightningcss-linux-x64-musl@npm:1.31.1": + version: 1.31.1 + resolution: "lightningcss-linux-x64-musl@npm:1.31.1" + conditions: os=linux & cpu=x64 & libc=musl + languageName: node + linkType: hard + +"lightningcss-win32-arm64-msvc@npm:1.31.1": + version: 1.31.1 + resolution: "lightningcss-win32-arm64-msvc@npm:1.31.1" + conditions: os=win32 & cpu=arm64 + languageName: node + linkType: hard + "lightningcss-win32-x64-msvc@npm:1.25.1": version: 1.25.1 resolution: "lightningcss-win32-x64-msvc@npm:1.25.1" @@ -33061,6 +33309,13 @@ __metadata: languageName: node linkType: hard +"lightningcss-win32-x64-msvc@npm:1.31.1": + version: 1.31.1 + resolution: "lightningcss-win32-x64-msvc@npm:1.31.1" + conditions: os=win32 & cpu=x64 + languageName: node + linkType: hard + "lightningcss@npm:1.25.1": version: 1.25.1 resolution: "lightningcss@npm:1.25.1" @@ -33098,6 +33353,49 @@ __metadata: languageName: node linkType: hard +"lightningcss@npm:^1.31.1": + version: 1.31.1 + resolution: "lightningcss@npm:1.31.1" + dependencies: + detect-libc: "npm:^2.0.3" + lightningcss-android-arm64: "npm:1.31.1" + lightningcss-darwin-arm64: "npm:1.31.1" + lightningcss-darwin-x64: "npm:1.31.1" + lightningcss-freebsd-x64: "npm:1.31.1" + lightningcss-linux-arm-gnueabihf: "npm:1.31.1" + lightningcss-linux-arm64-gnu: "npm:1.31.1" + lightningcss-linux-arm64-musl: "npm:1.31.1" + lightningcss-linux-x64-gnu: "npm:1.31.1" + lightningcss-linux-x64-musl: "npm:1.31.1" + lightningcss-win32-arm64-msvc: "npm:1.31.1" + lightningcss-win32-x64-msvc: "npm:1.31.1" + dependenciesMeta: + lightningcss-android-arm64: + optional: true + lightningcss-darwin-arm64: + optional: true + lightningcss-darwin-x64: + optional: true + lightningcss-freebsd-x64: + optional: true + lightningcss-linux-arm-gnueabihf: + optional: true + lightningcss-linux-arm64-gnu: + optional: true + lightningcss-linux-arm64-musl: + optional: true + lightningcss-linux-x64-gnu: + optional: true + lightningcss-linux-x64-musl: + optional: true + lightningcss-win32-arm64-msvc: + optional: true + lightningcss-win32-x64-msvc: + optional: true + checksum: 10c0/c6754b305d4a73652e472fc0d7d65384a6e16c336ea61068eca60de2a45bd5c30abbf012358b82eac56ee98b5d88028932cda5268ff61967cffa400b9e7ee2ba + languageName: node + linkType: hard + "lilconfig@npm:^3.1.3": version: 3.1.3 resolution: "lilconfig@npm:3.1.3" @@ -38931,7 +39229,7 @@ __metadata: languageName: node linkType: hard -"react-refresh@npm:0.18.0": +"react-refresh@npm:0.18.0, react-refresh@npm:^0.18.0": version: 0.18.0 resolution: "react-refresh@npm:0.18.0" checksum: 10c0/34a262f7fd803433a534f50deb27a148112a81adcae440c7d1cbae7ef14d21ea8f2b3d783e858cb7698968183b77755a38b4d4b5b1d79b4f4689c2f6d358fff2 @@ -40210,6 +40508,58 @@ __metadata: languageName: node linkType: hard +"rolldown@npm:1.0.0-rc.4": + version: 1.0.0-rc.4 + resolution: "rolldown@npm:1.0.0-rc.4" + dependencies: + "@oxc-project/types": "npm:=0.113.0" + "@rolldown/binding-android-arm64": "npm:1.0.0-rc.4" + "@rolldown/binding-darwin-arm64": "npm:1.0.0-rc.4" + "@rolldown/binding-darwin-x64": "npm:1.0.0-rc.4" + "@rolldown/binding-freebsd-x64": "npm:1.0.0-rc.4" + "@rolldown/binding-linux-arm-gnueabihf": "npm:1.0.0-rc.4" + "@rolldown/binding-linux-arm64-gnu": "npm:1.0.0-rc.4" + "@rolldown/binding-linux-arm64-musl": "npm:1.0.0-rc.4" + "@rolldown/binding-linux-x64-gnu": "npm:1.0.0-rc.4" + "@rolldown/binding-linux-x64-musl": "npm:1.0.0-rc.4" + "@rolldown/binding-openharmony-arm64": "npm:1.0.0-rc.4" + "@rolldown/binding-wasm32-wasi": "npm:1.0.0-rc.4" + "@rolldown/binding-win32-arm64-msvc": "npm:1.0.0-rc.4" + "@rolldown/binding-win32-x64-msvc": "npm:1.0.0-rc.4" + "@rolldown/pluginutils": "npm:1.0.0-rc.4" + dependenciesMeta: + "@rolldown/binding-android-arm64": + optional: true + "@rolldown/binding-darwin-arm64": + optional: true + "@rolldown/binding-darwin-x64": + optional: true + "@rolldown/binding-freebsd-x64": + optional: true + "@rolldown/binding-linux-arm-gnueabihf": + optional: true + "@rolldown/binding-linux-arm64-gnu": + optional: true + "@rolldown/binding-linux-arm64-musl": + optional: true + "@rolldown/binding-linux-x64-gnu": + optional: true + "@rolldown/binding-linux-x64-musl": + optional: true + "@rolldown/binding-openharmony-arm64": + optional: true + "@rolldown/binding-wasm32-wasi": + optional: true + "@rolldown/binding-win32-arm64-msvc": + optional: true + "@rolldown/binding-win32-x64-msvc": + optional: true + bin: + rolldown: bin/cli.mjs + checksum: 10c0/1336aedae71b5885036aeff6bbe9d6c58af205f72e8686edb2a3b27e6b4eb056f2f528b50373ad91567696369501f5b18eb99a926343bdb3012b7495962ddcfb + languageName: node + linkType: hard + "rollup@npm:4.55.3, rollup@npm:^4.34.9, rollup@npm:^4.35.0, rollup@npm:^4.43.0": version: 4.55.3 resolution: "rollup@npm:4.55.3" @@ -44671,6 +45021,65 @@ __metadata: languageName: node linkType: hard +"vite@npm:8.0.0-beta.14": + version: 8.0.0-beta.14 + resolution: "vite@npm:8.0.0-beta.14" + dependencies: + "@oxc-project/runtime": "npm:0.113.0" + fdir: "npm:^6.5.0" + fsevents: "npm:~2.3.3" + lightningcss: "npm:^1.31.1" + picomatch: "npm:^4.0.3" + postcss: "npm:^8.5.6" + rolldown: "npm:1.0.0-rc.4" + tinyglobby: "npm:^0.2.15" + peerDependencies: + "@types/node": ^20.19.0 || >=22.12.0 + "@vitejs/devtools": ^0.0.0-alpha.31 + esbuild: ^0.27.0 + jiti: ">=1.21.0" + less: ^4.0.0 + sass: ^1.70.0 + sass-embedded: ^1.70.0 + stylus: ">=0.54.8" + sugarss: ^5.0.0 + terser: ^5.16.0 + tsx: ^4.8.1 + yaml: ^2.4.2 + dependenciesMeta: + fsevents: + optional: true + peerDependenciesMeta: + "@types/node": + optional: true + "@vitejs/devtools": + optional: true + esbuild: + optional: true + jiti: + optional: true + less: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + tsx: + optional: true + yaml: + optional: true + bin: + vite: bin/vite.js + checksum: 10c0/e00e30071444a419f4cd35775af64c2fce5208a95eb05d50c9f646d6c32962bb0aa969dd07d44bc284f7c145846eaea98994864fd5100f45e7432db59e322c38 + languageName: node + linkType: hard + "vite@npm:^5.0.0 || ^6.0.0 || ^7.0.0-0, vite@npm:^6.0.0 || ^7.0.0": version: 7.1.12 resolution: "vite@npm:7.1.12" @@ -46158,27 +46567,6 @@ __metadata: languageName: node linkType: hard -"zustand@npm:5.0.8": - version: 5.0.8 - resolution: "zustand@npm:5.0.8" - peerDependencies: - "@types/react": ">=18.0.0" - immer: ">=9.0.6" - react: ">=18.0.0" - use-sync-external-store: ">=1.2.0" - peerDependenciesMeta: - "@types/react": - optional: true - immer: - optional: true - react: - optional: true - use-sync-external-store: - optional: true - checksum: 10c0/e865a6f7f1c0e03571701db5904151aaa7acefd6f85541a117085e129bf16e8f60b11f8cc82f277472de5c7ad5dcb201e1b0a89035ea0b40c9d9aab3cd24c8ad - languageName: node - linkType: hard - "zustand@npm:^4.4.1": version: 4.5.5 resolution: "zustand@npm:4.5.5"