From 22c631bc32b7a90780c73249b43bc8b2c1c53a45 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Mu=CC=88ller?= Date: Fri, 6 Sep 2024 15:41:56 +0200 Subject: [PATCH] feat(bop): implement path encoding --- .../bag-of-paths-encoder/src/encode-paths.ts | 28 ++++ .../encoder/bag-of-paths-encoder/src/index.ts | 44 ++---- .../bag-of-paths-encoder/src/mapper.ts | 85 ++++++++++ .../src/node-encodings.ts | 74 --------- .../bag-of-paths-encoder/src/template.ts | 64 ++++++++ .../test/template.test.ts | 10 ++ .../bag-of-paths-encoder/test/test-utils.ts | 2 +- packages/framework/plugin/src/parameters.ts | 25 ++- packages/visualizer/package.json | 1 + .../visualizer/src/components/Parameters.tsx | 69 ++++++-- .../bag-of-paths/BagOfPathsEncoding.tsx | 145 +---------------- pnpm-lock.yaml | 147 ++++++++++++++++-- 12 files changed, 413 insertions(+), 281 deletions(-) create mode 100644 packages/encoder/bag-of-paths-encoder/src/encode-paths.ts create mode 100644 packages/encoder/bag-of-paths-encoder/src/mapper.ts delete mode 100644 packages/encoder/bag-of-paths-encoder/src/node-encodings.ts create mode 100644 packages/encoder/bag-of-paths-encoder/src/template.ts create mode 100644 packages/encoder/bag-of-paths-encoder/test/template.test.ts diff --git a/packages/encoder/bag-of-paths-encoder/src/encode-paths.ts b/packages/encoder/bag-of-paths-encoder/src/encode-paths.ts new file mode 100644 index 00000000..e0f22cf4 --- /dev/null +++ b/packages/encoder/bag-of-paths-encoder/src/encode-paths.ts @@ -0,0 +1,28 @@ +import type { GraphModel, GraphNode } from '@cm2ml/ir' + +import type { PathData } from './paths' +import type { Template } from './template' +import { compileTemplate } from './template' + +export function encodePaths(paths: PathData[], model: GraphModel, templates: readonly string[]) { + const nodes = [...model.nodes] + const compiledTemplates = templates.map((template) => compileTemplate(template)) + return paths.map((path) => encodePath(path, nodes, compiledTemplates)) +} + +function encodePath(path: PathData, nodes: GraphNode[], templates: Template[]) { + const parts: string[] = [] + for (const step of path.steps) { + const node = nodes[step] + if (!node) { + throw new Error(`Node index out-of-bounds. This is an internal error.`) + } + const mapper = templates.find((template) => template.isApplicable(node)) + const mapped = mapper?.apply(node) + if (mapped !== undefined) { + parts.push(mapped) + } + } + // TODO/Jan: Also encode the edges + return parts.join(' -> ') +} diff --git a/packages/encoder/bag-of-paths-encoder/src/index.ts b/packages/encoder/bag-of-paths-encoder/src/index.ts index f527d15d..fdb9096a 100644 --- a/packages/encoder/bag-of-paths-encoder/src/index.ts +++ b/packages/encoder/bag-of-paths-encoder/src/index.ts @@ -5,15 +5,12 @@ import { batchTryCatch, compose, definePlugin } from '@cm2ml/plugin' import { Stream } from '@yeger/streams' import { pathWeightTypes, sortOrders, stepWeightTypes } from './bop-types' -import { encodeNode, nodeEncodingTypes } from './node-encodings' -import type { PathData } from './paths' +import { encodePaths } from './encode-paths' import { collectPaths } from './paths' export type { PathData } from './paths' export { stepWeightTypes, pathWeightTypes } export type { PathWeight, StepWeight } from './bop-types' -export { nodeEncodingTypes } -export type { NodeEncodingType, NodeEncoding, PathCounts } from './node-encodings' const PathBuilder = definePlugin({ name: 'path-builder', @@ -69,51 +66,34 @@ const PathBuilder = definePlugin({ description: 'Ordering of paths according to their weight', group: 'Filtering', }, - nodeEncoding: { + nodeTemplates: { type: 'list', unique: true, - allowedValues: nodeEncodingTypes, - defaultValue: [], - description: 'Encodings to apply to nodes', + ordered: true, + defaultValue: [ + 'type="Model"->{<<}{{name}}{>>}', + '{{name}}{ : }{{type}}', + ], + description: 'Template for encoding nodes of paths', group: 'Encoding', }, }, - invoke: ({ data, metadata: featureContext }: { data: GraphModel, metadata: FeatureContext }, parameters) => { + invoke: ({ data, metadata }: { data: GraphModel, metadata: FeatureContext }, parameters) => { const paths = collectPaths(data, parameters) const mapping = Stream .from(data.nodes) .map((node) => node.requireId()) .toArray() - const nodeData = Array.from(data.nodes).map((node, nodeIndex) => [node, getRelevantPaths(nodeIndex, paths)] as const) - const longestPathLength = paths.reduce((max, path) => Math.max(max, path.steps.length), 0) - const highestPathCount = nodeData.reduce((max, [_, paths]) => Math.max(max, paths.size), 0) - const additionalMetadata = parameters.nodeEncoding.includes('features') ? { nodeFeatures: featureContext.nodeFeatures, edgeFeatures: featureContext.edgeFeatures } : {} return { data: { paths, + encodedPaths: encodePaths(paths, data, parameters.nodeTemplates), mapping, - nodes: parameters.nodeEncoding.length > 0 - ? nodeData.map(([node, paths], nodeIndex) => - encodeNode({ - nodeIndex, - node, - featureContext, - paths, - parameters, - longestPathLength, - highestPathCount, - }, - ), - ) - : undefined, }, - metadata: { ...additionalMetadata, idAttribute: data.metamodel.idAttribute, typeAttributes: data.metamodel.typeAttributes }, + metadata: { ...metadata, idAttribute: data.metamodel.idAttribute, typeAttributes: data.metamodel.typeAttributes }, } }, }) -function getRelevantPaths(nodeIndex: number, paths: PathData[]) { - return Stream.from(paths).filter((path) => path.steps[0] === nodeIndex || path.steps.at(-1) === nodeIndex).toSet() -} - +// TODO/Jan: Remove feature encoder export const BagOfPathsEncoder = compose(FeatureEncoder, batchTryCatch(PathBuilder), 'bag-of-paths') diff --git a/packages/encoder/bag-of-paths-encoder/src/mapper.ts b/packages/encoder/bag-of-paths-encoder/src/mapper.ts new file mode 100644 index 00000000..6ecb4f37 --- /dev/null +++ b/packages/encoder/bag-of-paths-encoder/src/mapper.ts @@ -0,0 +1,85 @@ +import type { ModelMember } from '@cm2ml/ir' + +const separator = '@' + +export const attributeKeyword = `attribute${separator}` + +class AttributeValueSelector { + public constructor(public readonly attribute: string) { } + + public select(element: ModelMember) { + return element.getAttribute(this.attribute)?.value.literal + } + + public static fromString(selector: string) { + if (!selector.startsWith(attributeKeyword)) { + throw new Error(`Invalid selector: ${selector}`) + } + const attribute = selector.slice(attributeKeyword.length) + if (attribute.includes(separator)) { + throw new Error(`Invalid attribute: ${attribute}`) + } + return new AttributeValueSelector(attribute) + } +} + +const directAccessKeys = ['id', 'type', 'tag', 'name'] as const +type DirectAccessKey = typeof directAccessKeys[number] +function isDirectAccessKey(key: string): key is DirectAccessKey { + return directAccessKeys.includes(key as DirectAccessKey) +} + +class DirectSelector { + public constructor(public readonly key: DirectAccessKey) { } + + public select(element: ModelMember) { + return element[this.key] + } + + public static fromString(selector: string) { + if (!isDirectAccessKey(selector)) { + return null + } + return new DirectSelector(selector) + } +} + +export function getSelector(selector: string) { + return DirectSelector.fromString(selector) ?? AttributeValueSelector.fromString(selector) +} + +export type Selector = ReturnType + +class EqualityFilter { + public constructor(public readonly selector: Selector, public readonly comparator: '=', public readonly target: string | Selector) { } + + public matches(element: ModelMember) { + const selected = this.selector.select(element) + const resolvedTarget = typeof this.target === 'string' ? this.target : this.target.select(element) + return selected === resolvedTarget + } + + public static fromString(filter: string) { + const parts = filter.split('=') + if (parts.length !== 2) { + throw new Error(`Invalid filter: ${filter}`) + } + const selector = getSelector(parts[0]!) + if (selector === null) { + throw new Error(`Invalid filter: ${filter}`) + } + const target = parts[1]! + if (target.at(0) === '"' && target.at(-1) === '"') { + return new EqualityFilter(selector, '=', target.slice(1, -1)) + } + const targetSelector = getSelector(target) + if (!targetSelector) { + throw new Error(`Invalid filter: ${filter}`) + } + return new EqualityFilter(selector, '=', targetSelector) + } +} + +export function getFilter(filter: string) { + return EqualityFilter.fromString(filter) +} diff --git a/packages/encoder/bag-of-paths-encoder/src/node-encodings.ts b/packages/encoder/bag-of-paths-encoder/src/node-encodings.ts deleted file mode 100644 index 78e12b3d..00000000 --- a/packages/encoder/bag-of-paths-encoder/src/node-encodings.ts +++ /dev/null @@ -1,74 +0,0 @@ -import type { FeatureContext } from '@cm2ml/feature-encoder' -import type { GraphNode } from '@cm2ml/ir' -import { Stream } from '@yeger/streams' - -import type { PathData } from './paths' - -export const nodeEncodingTypes = [ - 'features', - 'path-count', - 'path-weight', - // 'discounted-path-sum', -] as const - -export type NodeEncodingType = typeof nodeEncodingTypes[number] - -export interface NodeEncodingParameters { - nodeEncoding: readonly (NodeEncodingType | string & Record) [] -} - -interface NodeEncoderContext { - nodeIndex: number - node: GraphNode - featureContext: FeatureContext - paths: Set - parameters: NodeEncodingParameters - /** The length of the longest path of the model. */ - longestPathLength: number - /** The highest number of paths for any node in the model. */ - highestPathCount: number -} - -export type NodeEncoding = ReturnType - -export function encodeNode(context: NodeEncoderContext) { - const pathEncodingTypes = new Set(context.parameters.nodeEncoding) - function withSelected(name: NodeEncodingType, encoder: (context: NodeEncoderContext) => T) { - return pathEncodingTypes.has(name) ? encoder(context) : undefined - } - return { - 'features': withSelected('features', encodeNodeAttributes), - 'path-count': withSelected('path-count', encodePathCount), - 'path-weight': withSelected('path-weight', encodePathWeight), - } satisfies Record -} - -/** - * Feature vector for the node's attributes. - */ -function encodeNodeAttributes(context: NodeEncoderContext) { - return context.featureContext.getNodeFeatureVector(context.node) -} - -/** - * Feature vector for the number of paths of each length. - * Index is offset by one, e.g., the number of paths of length 1 is at index 0. - */ -export type PathCounts = number[] - -function encodePathCount(context: NodeEncoderContext): PathCounts { - return Array.from({ length: context.longestPathLength }).map((_, length) => { - return Stream.from(context.paths).filter((path) => path.steps.length === length + 1).toArray().length - }) -} - -/** - * Feature vector for the weights of the paths, right-padded with zeros. - */ -export type PathWeights = number[] - -function encodePathWeight(context: NodeEncoderContext): PathWeights { - const weights = Stream.from(context.paths).map((path) => path.weight) - const padding = Array.from({ length: context.highestPathCount - context.paths.size }).map(() => 0) - return weights.concat(padding).toArray() -} diff --git a/packages/encoder/bag-of-paths-encoder/src/template.ts b/packages/encoder/bag-of-paths-encoder/src/template.ts new file mode 100644 index 00000000..78587570 --- /dev/null +++ b/packages/encoder/bag-of-paths-encoder/src/template.ts @@ -0,0 +1,64 @@ +import type { ModelMember } from '@cm2ml/ir' +import { Stream } from '@yeger/streams' + +import { getFilter, getSelector } from './mapper' + +export interface Template { + isApplicable: (modelMember: ModelMember) => boolean + apply: (modelMember: ModelMember) => string +} + +export function compileTemplate(rawInput: string): Template { + const { rawFilter, rawTemplate } = extractFilter(rawInput) + if (!rawTemplate) { + throw new Error(`Invalid template: ${rawInput}`) + } + const segments = getSegments(rawTemplate) + if (!segments) { + throw new Error(`Invalid template: ${rawInput}`) + } + const replacers = segments.map(parseSegment) + const f = rawFilter ? getFilter(rawFilter) : undefined + return { + isApplicable: (modelMember: ModelMember) => !f || f.matches(modelMember), + apply: (modelMember: ModelMember) => Stream.from(replacers).map((replace) => replace(modelMember)).filterNonNull().join(''), + } +} + +function extractFilter(template: string) { + const parts = template.split('->') + if (parts.length === 0) { + return {} + } + if (parts.length === 1) { + return { rawTemplate: parts[0]! } + } + if (parts.length === 2) { + return { rawFilter: parts[0]!, rawTemplate: parts[1]! } + } + throw new Error(`Invalid template: ${template}`) +} + +export function getSegments(template: string) { + const parts = template.split('}{') + if (parts.length === 0) { + return null + } + parts[0] = parts[0]!.slice(1) + parts[parts.length - 1] = parts[parts.length - 1]!.slice(0, -1) + return parts +} + +export function parseSegment(segment: string) { + if (segment.startsWith('{') && segment.endsWith('}')) { + const mapper = getSelector(segment.slice(1, -1)) + return (modelMember: ModelMember): string | undefined => { + const mapped = mapper.select(modelMember) + if (Array.isArray(mapped)) { + return mapped.join('') + } + return mapped + } + } + return () => segment +} diff --git a/packages/encoder/bag-of-paths-encoder/test/template.test.ts b/packages/encoder/bag-of-paths-encoder/test/template.test.ts new file mode 100644 index 00000000..0f9867a5 --- /dev/null +++ b/packages/encoder/bag-of-paths-encoder/test/template.test.ts @@ -0,0 +1,10 @@ +import { describe, expect, it } from 'vitest' + +import { getSegments } from '../src/template' + +describe('mapper', () => { + it('can parse a template', () => { + const result = getSegments('{a}{b}') + expect(result).toEqual(['a', 'b']) + }) +}) diff --git a/packages/encoder/bag-of-paths-encoder/test/test-utils.ts b/packages/encoder/bag-of-paths-encoder/test/test-utils.ts index 48c26a3f..0ace2b18 100644 --- a/packages/encoder/bag-of-paths-encoder/test/test-utils.ts +++ b/packages/encoder/bag-of-paths-encoder/test/test-utils.ts @@ -17,7 +17,7 @@ export function createTestModel(nodes: string[], edges: [string, string][] | (re const graphNode = model.addNode('node') graphNode.id = id graphNode.type = 'node' - graphNode.parent = root + root.addChild(graphNode) }) edges.forEach(([sourceId, targetId]) => { const source = model.getNodeById(sourceId) diff --git a/packages/framework/plugin/src/parameters.ts b/packages/framework/plugin/src/parameters.ts index 77592fff..8a145ac4 100644 --- a/packages/framework/plugin/src/parameters.ts +++ b/packages/framework/plugin/src/parameters.ts @@ -43,7 +43,7 @@ export type ListParameter = ParameterBase & Readonly< * If false, the items will be sorted. */ readonly ordered?: boolean - } & ({ readonly unique?: false, readonly ordered?: boolean } | { readonly unique: true, readonly ordered?: false }) + } & ({ readonly unique?: boolean, readonly ordered?: false } | { readonly unique: true, readonly ordered: true }) > export type Parameter = PrimitiveParameter | ListParameter @@ -62,13 +62,26 @@ function getZodValidator(parameter: Parameter) { case 'list': { const baseArray = parameter.allowedValues && parameter.allowedValues.length > 0 ? z.array(z.enum(parameter.allowedValues as [string, ...string[]])) : z.array(z.string()) return baseArray.default([...parameter.defaultValue]).transform((value) => { - if (parameter.unique) { - return [...new Set(value)] + if (!parameter.ordered && !parameter.unique) { + return value.toSorted() } - if (!parameter.ordered) { - return value.sort() + if (parameter.ordered && !parameter.unique) { + throw new Error('Ordered lists must be unique') } - return value + if (parameter.ordered && parameter.unique) { + const visited = new Set() + return value.filter((item) => { + if (visited.has(item)) { + return false + } + visited.add(item) + return true + }) + } + if (!parameter.ordered && parameter.unique) { + return [...new Set(value)].toSorted() + } + throw new Error('Invalid list configuration. This is an internal error.') }) } } diff --git a/packages/visualizer/package.json b/packages/visualizer/package.json index f19a5314..155d45db 100644 --- a/packages/visualizer/package.json +++ b/packages/visualizer/package.json @@ -33,6 +33,7 @@ "@cm2ml/ir": "workspace:*", "@cm2ml/plugin": "workspace:*", "@cm2ml/utils": "workspace:*", + "@hello-pangea/dnd": "16.6.0", "@radix-ui/react-accordion": "1.2.0", "@radix-ui/react-checkbox": "1.1.1", "@radix-ui/react-collapsible": "1.1.0", diff --git a/packages/visualizer/src/components/Parameters.tsx b/packages/visualizer/src/components/Parameters.tsx index 5b10ca91..a2b09e90 100644 --- a/packages/visualizer/src/components/Parameters.tsx +++ b/packages/visualizer/src/components/Parameters.tsx @@ -1,10 +1,12 @@ import type { Parameter, ParameterMetadata, ParameterType } from '@cm2ml/plugin' -import { CaretSortIcon, Cross1Icon, SymbolIcon, TrashIcon } from '@radix-ui/react-icons' +import type { OnDragEndResponder } from '@hello-pangea/dnd' +import { DragDropContext, Draggable, Droppable } from '@hello-pangea/dnd' +import { CaretSortIcon, Cross1Icon, DragHandleHorizontalIcon, SymbolIcon, TrashIcon } from '@radix-ui/react-icons' import { Stream } from '@yeger/streams' import { useMemo, useState } from 'react' import { displayName } from '../lib/displayName' -import { getNewParameters } from '../lib/utils' +import { cn, getNewParameters } from '../lib/utils' import { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from './ui/accordion' import { Button } from './ui/button' @@ -297,6 +299,19 @@ function StringListInput({ }} /> ) + const processedItems = parameter.ordered ? values : values.toSorted() + const onDragEnd: OnDragEndResponder = (result) => { + if (!result.destination) { + return + } + const reordered = Array.from(processedItems) + const [removed] = reordered.splice(result.source.index, 1) + if (!removed) { + return + } + reordered.splice(result.destination.index, 0, removed) + onChange(reordered) + } return ( @@ -319,17 +334,45 @@ function StringListInput({ Clear - { - (parameter.ordered ? values : values.toSorted()).map((value, index) => ( - // eslint-disable-next-line react/no-array-index-key -
- - {value} -
- )) - } + + + {(provided, snapshot) => ( +
+ { + processedItems.map((value, index) => ( + ( + + {(provided) => ( +
+ + {value} +
+ +
+ +
+ +
+ )} + + ) + )) + } + {provided.placeholder} +
+ )} + + diff --git a/packages/visualizer/src/components/encoder/encodings/bag-of-paths/BagOfPathsEncoding.tsx b/packages/visualizer/src/components/encoder/encodings/bag-of-paths/BagOfPathsEncoding.tsx index f7bbc4c7..cd665315 100644 --- a/packages/visualizer/src/components/encoder/encodings/bag-of-paths/BagOfPathsEncoding.tsx +++ b/packages/visualizer/src/components/encoder/encodings/bag-of-paths/BagOfPathsEncoding.tsx @@ -1,19 +1,11 @@ -import type { NodeEncoding, NodeEncodingType } from '@cm2ml/builtin' import { BagOfPathsEncoder } from '@cm2ml/builtin' import type { GraphModel } from '@cm2ml/ir' import { ExecutionError } from '@cm2ml/plugin' -import { Stream } from '@yeger/streams' -import { Fragment, useEffect, useMemo, useState } from 'react' +import { Fragment } from 'react' -import { displayName } from '../../../../lib/displayName' -import type { Selection } from '../../../../lib/useSelection' -import { useSelection } from '../../../../lib/useSelection' import type { ParameterValues } from '../../../Parameters' -import { SelectButton } from '../../../SelectButton' import { Hint } from '../../../ui/hint' -import { ResizableHandle, ResizablePanel, ResizablePanelGroup } from '../../../ui/resizable' import { Separator } from '../../../ui/separator' -import { Tabs, TabsContent, TabsList, TabsTrigger } from '../../../ui/tabs' import { useEncoder } from '../../useEncoder' import { PathGraph } from './PathGraph' @@ -31,11 +23,11 @@ export function BagOfPathsEncoding({ model, parameters }: Props) { if (encoding.data instanceof ExecutionError) { return } - const { paths, mapping, nodes } = encoding.data + const { paths, mapping } = encoding.data if (paths.length === 0) { return } - const pathGraphList = ( + return (
{paths.map((path, i) => ( // eslint-disable-next-line react/no-array-index-key @@ -46,135 +38,4 @@ export function BagOfPathsEncoding({ model, parameters }: Props) { ))}
) - if (!nodes || nodes.length === 0 || Object.values(nodes[0]!).every((encoding) => encoding === undefined)) { - return pathGraphList - } - return ( - - - {pathGraphList} - - - - - - - ) -} - -interface NodeEncodingsProps { - nodes: NodeEncoding[] - mapping: string[] -} - -function NodeEncodings({ nodes, mapping }: NodeEncodingsProps) { - const encodingTypes = useMemo(() => Stream.fromObject(nodes[0] ?? {}).filter((encoding) => encoding[1] !== undefined).map(([name]) => name).distinct().toArray() as NodeEncodingType[], [nodes]) - const [tabValue, setTabValue] = useState(encodingTypes[0]) - useEffect(() => { - if (!encodingTypes.includes(tabValue as NodeEncodingType)) { - setTabValue(encodingTypes[0]) - } - }, [encodingTypes]) - return ( - - {encodingTypes.length > 1 - ? ( -
- - {encodingTypes.map((encodingType) => - {displayName(encodingType)}, - )} - -
- ) - : null} - {encodingTypes.map((encodingType) => ( - - - - ), - )} -
- ) -} - -interface NodeEncodingListProps { - encodingType: NodeEncodingType - nodes: NodeEncoding[] - mapping: string[] -} - -function NodeEncodingList({ encodingType, nodes, mapping }: NodeEncodingListProps) { - const formattedVectors = useMemo(() => { - const firstRow = nodes[0]?.[encodingType] - if (!firstRow) { - return [] - } - const vectorLength = firstRow.length - const maxDigits = Array.from({ length: vectorLength }, (_, i) => - Math.max(...nodes.map((node) => formatValue(node[encodingType]![i]!).length))) - return nodes.map((vectors, nodeIndex) => { - const nodeId = mapping[nodeIndex]! - const paddedVector = vectors[encodingType]!.map((value, i) => formatValue(value).padStart(maxDigits[i]!, ' ')) - return { - nodeId, - vector: `[${paddedVector.join(', ')}]`, - } - }) - }, [encodingType, nodes]) - - return ( -
- {formattedVectors.map(({ nodeId, vector }) => )} -
- ) -} - -function formatValue(value: boolean | number | string | null) { - if (value === null) { - return '' - } - if (typeof value === 'boolean') { - return value ? 'true' : 'false' - } - if (typeof value === 'number') { - const moduloOne = value % 1 - if (moduloOne === 0) { - return value.toFixed(0) - } - if (Number.isNaN(moduloOne)) { - return 'NaN' - } - return value.toFixed(2) - } - return `${value}` -} - -interface NodeEncodingRowProps { - nodeId: string - vector: string -} - -function NodeEncodingRow({ nodeId, vector }: NodeEncodingRowProps) { - const selection = useSelection.use.selection() - const isNodeSelected = useMemo(() => { - if (!selection) { - return false - } - if (selection.type === 'nodes') { - return selection.nodes.includes(nodeId) - } - return selection.edges.some(([source, target]) => source === nodeId || target === nodeId) - }, [nodeId, selection]) - const newSelection = useMemo(() => ({ - type: 'nodes', - nodes: [nodeId], - origin: 'path', - }), [nodeId]) - return ( - - -
{vector}
-
- ) } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 0935bfad..7c3efa06 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -857,6 +857,9 @@ importers: '@cm2ml/utils': specifier: workspace:* version: link:../utils + '@hello-pangea/dnd': + specifier: 16.6.0 + version: 16.6.0(@types/react-dom@18.3.0)(@types/react@18.3.5)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@radix-ui/react-accordion': specifier: 1.2.0 version: 1.2.0(@types/react-dom@18.3.0)(@types/react@18.3.5)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) @@ -1796,10 +1799,6 @@ packages: resolution: {integrity: sha512-SAj8oKi8UogVi6eXQXKNPu8qZ78Yzy7zawrlTr0M+IuW/g8Qe9gVDhGcF9h1S69OyACpYoLxEzpjs1M15sI5wQ==} engines: {node: '>=6.9.0'} - '@babel/runtime@7.24.0': - resolution: {integrity: sha512-Chk32uHMg6TnQdvw2e9IlqPpFX/6NLuK0Ys2PqLb7/gL5uFn9mXvK715FGLlOLQrcO4qIkNHkvPGktzzXexsFw==} - engines: {node: '>=6.9.0'} - '@babel/runtime@7.24.5': resolution: {integrity: sha512-Nms86NXrsaeU9vbBJKni6gXiEXZ4CVpYVzEjDH9Sb8vmZ3UljyA1GSOJl/6LGPO8EHLuSF9H+IxNXHPX8QHJ4g==} engines: {node: '>=6.9.0'} @@ -2228,6 +2227,12 @@ packages: '@floating-ui/utils@0.1.6': resolution: {integrity: sha512-OfX7E2oUDYxtBvsuS4e/jSn4Q9Qb6DzgeYtsAdkPZ47znpoNsMgZw0+tVijiv3uGNR6dgNlty6r9rzIzHjtd/A==} + '@hello-pangea/dnd@16.6.0': + resolution: {integrity: sha512-vfZ4GydqbtUPXSLfAvKvXQ6xwRzIjUSjVU0Sx+70VOhc2xx6CdmJXJ8YhH70RpbTUGjxctslQTHul9sIOxCfFQ==} + peerDependencies: + react: ^16.8.5 || ^17.0.0 || ^18.0.0 + react-dom: ^16.8.5 || ^17.0.0 || ^18.0.0 + '@humanwhocodes/module-importer@1.0.1': resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==} engines: {node: '>=12.22'} @@ -3389,6 +3394,9 @@ packages: '@types/hammerjs@2.0.45': resolution: {integrity: sha512-qkcUlZmX6c4J8q45taBKTL3p+LbITgyx7qhlPYOdOHZB7B31K0mXbP5YA7i7SgDeEGuI9MnumiKPEMrxg8j3KQ==} + '@types/hoist-non-react-statics@3.3.5': + resolution: {integrity: sha512-SbcrWzkKBw2cdwRTwQAswfpB9g9LJWfjtUeW/jvNwbhC8cpmmNYVePa+ncbUe0rGTQ7G3Ff6mYUN2VMfLVr+Sg==} + '@types/inquirer@6.5.0': resolution: {integrity: sha512-rjaYQ9b9y/VFGOpqBEXRavc3jh0a+e6evAbI31tMda8VlPaSy0AZJfXsvmIe3wklc7W6C3zCSfleuMXR7NOyXw==} @@ -3449,6 +3457,9 @@ packages: '@types/unist@2.0.8': resolution: {integrity: sha512-d0XxK3YTObnWVp6rZuev3c49+j4Lo8g4L1ZRm9z5L0xpoZycUPshHgczK5gsUMaZOstjVYYi09p5gYvUtfChYw==} + '@types/use-sync-external-store@0.0.3': + resolution: {integrity: sha512-EwmlvuaxPNej9+T4v5AuBPJa2x2UOJVdjCtDHgcDqitUeOtjnJKJ+apYjVcAoBEMjKW1VVFGZLUb5+qqa09XFA==} + '@typescript-eslint/eslint-plugin@8.3.0': resolution: {integrity: sha512-FLAIn63G5KH+adZosDYiutqkOkYEx0nvcwNNfJAf+c7Ae/H35qWwTYvPZUKFj5AS+WfHG/WJJfWnDnyNUlp8UA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -4319,6 +4330,9 @@ packages: resolution: {integrity: sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA==} engines: {node: '>=8'} + css-box-model@1.2.1: + resolution: {integrity: sha512-a7Vr4Q/kd/aw96bnJG332W9V9LkJO69JRcaCYDUqjp6/z0w6VcZjgAcTbgFxEPfBgdnAwlh3iwu+hLopa+flJw==} + cssesc@3.0.0: resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==} engines: {node: '>=4'} @@ -5584,6 +5598,9 @@ packages: resolution: {integrity: sha512-kUGoI3p7u6B41z/dp33G6OaL7J4DRqRYwVmeIlwLClx7yaaAy7hoDExnuejTKtuDwfcatGmddHDEOjf6EyIxtQ==} engines: {node: '>=10.0.0'} + hoist-non-react-statics@3.3.2: + resolution: {integrity: sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==} + hosted-git-info@2.8.9: resolution: {integrity: sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==} @@ -6407,6 +6424,9 @@ packages: mdast-util-to-string@2.0.0: resolution: {integrity: sha512-AW4DRS3QbBayY/jJmD8437V1Gombjf8RSOUCMFBuo5iHi58AGEgVCKQ+ezHkZZDpAQS75hcBMpLqjpJTjtUL7w==} + memoize-one@6.0.0: + resolution: {integrity: sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw==} + meow@12.1.1: resolution: {integrity: sha512-BhXM0Au22RwUneMPwSCnyhTOizdWoIEPU9sp0Aqa1PnDMR5Wv2FGXYDjuzJEIX+Eo2Rb8xuYe5jrnm5QowQFkw==} engines: {node: '>=16.10'} @@ -7185,6 +7205,9 @@ packages: quick-format-unescaped@4.0.4: resolution: {integrity: sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==} + raf-schd@4.0.3: + resolution: {integrity: sha512-tQkJl2GRWh83ui2DiPTJz9wEiMN20syf+5oKfB03yYP7ioZcJwsIK8FjrtLwH1m7C7e+Tt2yYBlrOpdT+dyeIQ==} + randombytes@2.1.0: resolution: {integrity: sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==} @@ -7197,6 +7220,33 @@ packages: peerDependencies: react: ^18.3.1 + react-is@16.13.1: + resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==} + + react-is@18.3.1: + resolution: {integrity: sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==} + + react-redux@8.1.3: + resolution: {integrity: sha512-n0ZrutD7DaX/j9VscF+uTALI3oUPa/pO4Z3soOBIjuRn/FzVu6aehhysxZCLi6y7duMf52WNZGMl7CtuK5EnRw==} + peerDependencies: + '@types/react': ^16.8 || ^17.0 || ^18.0 + '@types/react-dom': ^16.8 || ^17.0 || ^18.0 + react: ^16.8 || ^17.0 || ^18.0 + react-dom: ^16.8 || ^17.0 || ^18.0 + react-native: '>=0.59' + redux: ^4 || ^5.0.0-beta.0 + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + react-dom: + optional: true + react-native: + optional: true + redux: + optional: true + react-refresh@0.14.2: resolution: {integrity: sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA==} engines: {node: '>=0.10.0'} @@ -7296,6 +7346,9 @@ packages: resolution: {integrity: sha512-qtEDqIZGVcSZCHniWwZWbRy79Dc6Wp3kT/UmDA2RJKBPg7+7k51aQBZirHmUGn5uvHf2rg8DkjizrN26k61ATw==} engines: {node: '>= 4'} + redux@4.2.1: + resolution: {integrity: sha512-LAUYz4lc+Do8/g7aeRa8JkyDErK6ekstQaqWQrNRW//MY1TvCEpMtpTWvlQ+FPbWCx+Xixu/6SHt5N0HR+SB4w==} + refa@0.12.1: resolution: {integrity: sha512-J8rn6v4DBb2nnFqkqwy6/NnTYMcgLA+sLr0iIO41qpv0n+ngb7ksag2tMRl0inb1bbO/esUwzW1vbJi7K0sI0g==} engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} @@ -8142,6 +8195,9 @@ packages: resolution: {integrity: sha512-Kw36UHxJEELq2VUqdaSGR2/8cAsPgMtvX8uGVU6Jk26O66PhXec0A5ZnRYs47btbtwPDpXXF66+Fo3vimCM9aQ==} engines: {node: '>=16'} + tiny-invariant@1.3.3: + resolution: {integrity: sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==} + tinybench@2.8.0: resolution: {integrity: sha512-1/eK7zUnIklz4JUUlL+658n58XO2hHLQfSk1Zf2LKieUjxidN16eKFEoDEfjHc3ohofSSqK3X5yO6VGb6iW8Lw==} @@ -8502,6 +8558,11 @@ packages: '@types/react': optional: true + use-memo-one@1.1.3: + resolution: {integrity: sha512-g66/K7ZQGYrI6dy8GLpVcMsBp4s17xNkYJVSMvTEevGy3nDxHOfE6z8BVE22+5G5x7t3+bhzrlTDB7ObrEE0cQ==} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + use-sidecar@1.1.2: resolution: {integrity: sha512-epTbsLuzZ7lPClpz2TyryBfztm7m+28DlEv2ZCQ3MDr5ssiwyOwGH/e5F9CkfWjJ1t4clvI58yF822/GUkjjhw==} engines: {node: '>=10'} @@ -9803,10 +9864,6 @@ snapshots: core-js-pure: 3.32.2 regenerator-runtime: 0.14.0 - '@babel/runtime@7.24.0': - dependencies: - regenerator-runtime: 0.14.0 - '@babel/runtime@7.24.5': dependencies: regenerator-runtime: 0.14.0 @@ -10429,6 +10486,22 @@ snapshots: '@floating-ui/utils@0.1.6': {} + '@hello-pangea/dnd@16.6.0(@types/react-dom@18.3.0)(@types/react@18.3.5)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@babel/runtime': 7.24.5 + css-box-model: 1.2.1 + memoize-one: 6.0.0 + raf-schd: 4.0.3 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + react-redux: 8.1.3(@types/react-dom@18.3.0)(@types/react@18.3.5)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(redux@4.2.1) + redux: 4.2.1 + use-memo-one: 1.1.3(react@18.3.1) + transitivePeerDependencies: + - '@types/react' + - '@types/react-dom' + - react-native + '@humanwhocodes/module-importer@1.0.1': {} '@humanwhocodes/retry@0.3.0': {} @@ -10731,7 +10804,7 @@ snapshots: '@radix-ui/react-dialog@1.0.5(@types/react-dom@18.3.0)(@types/react@18.3.5)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: - '@babel/runtime': 7.24.0 + '@babel/runtime': 7.24.5 '@radix-ui/primitive': 1.0.1 '@radix-ui/react-compose-refs': 1.0.1(@types/react@18.3.5)(react@18.3.1) '@radix-ui/react-context': 1.0.1(@types/react@18.3.5)(react@18.3.1) @@ -10999,7 +11072,7 @@ snapshots: '@radix-ui/react-primitive@1.0.3(@types/react-dom@18.3.0)(@types/react@18.3.5)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: - '@babel/runtime': 7.24.0 + '@babel/runtime': 7.24.5 '@radix-ui/react-slot': 1.0.2(@types/react@18.3.5)(react@18.3.1) react: 18.3.1 react-dom: 18.3.1(react@18.3.1) @@ -11708,6 +11781,11 @@ snapshots: '@types/hammerjs@2.0.45': {} + '@types/hoist-non-react-statics@3.3.5': + dependencies: + '@types/react': 18.3.5 + hoist-non-react-statics: 3.3.2 + '@types/inquirer@6.5.0': dependencies: '@types/through': 0.0.31 @@ -11770,6 +11848,8 @@ snapshots: '@types/unist@2.0.8': {} + '@types/use-sync-external-store@0.0.3': {} + '@typescript-eslint/eslint-plugin@8.3.0(@typescript-eslint/parser@8.3.0(eslint@9.9.1(jiti@1.21.6))(typescript@5.5.4))(eslint@9.9.1(jiti@1.21.6))(typescript@5.5.4)': dependencies: '@eslint-community/regexpp': 4.11.0 @@ -12057,7 +12137,7 @@ snapshots: eslint-plugin-astro: 1.2.3(eslint@9.9.1(jiti@1.21.6))(typescript@5.5.4) eslint-plugin-cypress: 3.5.0(eslint@9.9.1(jiti@1.21.6)) eslint-plugin-format: 0.1.2(eslint@9.9.1(jiti@1.21.6)) - eslint-plugin-import: 2.29.1(@typescript-eslint/parser@8.3.0(eslint@9.9.1(jiti@1.21.6))(typescript@5.5.4))(eslint-import-resolver-typescript@3.6.3)(eslint@9.9.1(jiti@1.21.6)) + eslint-plugin-import: 2.29.1(@typescript-eslint/parser@8.3.0(eslint@9.9.1(jiti@1.21.6))(typescript@5.5.4))(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@8.3.0(eslint@9.9.1(jiti@1.21.6))(typescript@5.5.4))(eslint-plugin-import-x@4.1.1(eslint@9.9.1(jiti@1.21.6))(typescript@5.5.4))(eslint-plugin-import@2.29.1(@typescript-eslint/parser@8.3.0(eslint@9.9.1(jiti@1.21.6))(typescript@5.5.4))(eslint@9.9.1(jiti@1.21.6)))(eslint@9.9.1(jiti@1.21.6)))(eslint@9.9.1(jiti@1.21.6)) eslint-plugin-jsx-a11y: 6.9.0(eslint@9.9.1(jiti@1.21.6)) eslint-plugin-markdown: 5.1.0(eslint@9.9.1(jiti@1.21.6)) eslint-plugin-md: 1.0.19 @@ -12841,6 +12921,10 @@ snapshots: crypto-random-string@2.0.0: {} + css-box-model@1.2.1: + dependencies: + tiny-invariant: 1.3.3 + cssesc@3.0.0: {} csstype@3.1.3: {} @@ -13363,7 +13447,7 @@ snapshots: is-bun-module: 1.1.0 is-glob: 4.0.3 optionalDependencies: - eslint-plugin-import: 2.29.1(@typescript-eslint/parser@8.3.0(eslint@9.9.1(jiti@1.21.6))(typescript@5.5.4))(eslint-import-resolver-typescript@3.6.3)(eslint@9.9.1(jiti@1.21.6)) + eslint-plugin-import: 2.29.1(@typescript-eslint/parser@8.3.0(eslint@9.9.1(jiti@1.21.6))(typescript@5.5.4))(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@8.3.0(eslint@9.9.1(jiti@1.21.6))(typescript@5.5.4))(eslint-plugin-import-x@4.1.1(eslint@9.9.1(jiti@1.21.6))(typescript@5.5.4))(eslint-plugin-import@2.29.1(@typescript-eslint/parser@8.3.0(eslint@9.9.1(jiti@1.21.6))(typescript@5.5.4))(eslint@9.9.1(jiti@1.21.6)))(eslint@9.9.1(jiti@1.21.6)))(eslint@9.9.1(jiti@1.21.6)) eslint-plugin-import-x: 4.1.1(eslint@9.9.1(jiti@1.21.6))(typescript@5.5.4) transitivePeerDependencies: - '@typescript-eslint/parser' @@ -13464,7 +13548,7 @@ snapshots: - supports-color - typescript - eslint-plugin-import@2.29.1(@typescript-eslint/parser@8.3.0(eslint@9.9.1(jiti@1.21.6))(typescript@5.5.4))(eslint-import-resolver-typescript@3.6.3)(eslint@9.9.1(jiti@1.21.6)): + eslint-plugin-import@2.29.1(@typescript-eslint/parser@8.3.0(eslint@9.9.1(jiti@1.21.6))(typescript@5.5.4))(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@8.3.0(eslint@9.9.1(jiti@1.21.6))(typescript@5.5.4))(eslint-plugin-import-x@4.1.1(eslint@9.9.1(jiti@1.21.6))(typescript@5.5.4))(eslint-plugin-import@2.29.1(@typescript-eslint/parser@8.3.0(eslint@9.9.1(jiti@1.21.6))(typescript@5.5.4))(eslint@9.9.1(jiti@1.21.6)))(eslint@9.9.1(jiti@1.21.6)))(eslint@9.9.1(jiti@1.21.6)): dependencies: array-includes: 3.1.8 array.prototype.findlastindex: 1.2.3 @@ -14490,6 +14574,10 @@ snapshots: heap-js@2.5.0: {} + hoist-non-react-statics@3.3.2: + dependencies: + react-is: 16.13.1 + hosted-git-info@2.8.9: {} hosted-git-info@7.0.1: @@ -15291,6 +15379,8 @@ snapshots: mdast-util-to-string@2.0.0: {} + memoize-one@6.0.0: {} + meow@12.1.1: {} merge-stream@2.0.0: {} @@ -16115,6 +16205,8 @@ snapshots: quick-format-unescaped@4.0.4: {} + raf-schd@4.0.3: {} + randombytes@2.1.0: dependencies: safe-buffer: 5.2.1 @@ -16132,6 +16224,25 @@ snapshots: react: 18.3.1 scheduler: 0.23.2 + react-is@16.13.1: {} + + react-is@18.3.1: {} + + react-redux@8.1.3(@types/react-dom@18.3.0)(@types/react@18.3.5)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(redux@4.2.1): + dependencies: + '@babel/runtime': 7.24.5 + '@types/hoist-non-react-statics': 3.3.5 + '@types/use-sync-external-store': 0.0.3 + hoist-non-react-statics: 3.3.2 + react: 18.3.1 + react-is: 18.3.1 + use-sync-external-store: 1.2.2(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.5 + '@types/react-dom': 18.3.0 + react-dom: 18.3.1(react@18.3.1) + redux: 4.2.1 + react-refresh@0.14.2: {} react-remove-scroll-bar@2.3.4(@types/react@18.3.5)(react@18.3.1): @@ -16253,6 +16364,10 @@ snapshots: source-map: 0.6.1 tslib: 2.6.2 + redux@4.2.1: + dependencies: + '@babel/runtime': 7.24.5 + refa@0.12.1: dependencies: '@eslint-community/regexpp': 4.11.0 @@ -17458,6 +17573,8 @@ snapshots: tightrope@0.2.0: {} + tiny-invariant@1.3.3: {} + tinybench@2.8.0: {} tinycolor2@1.6.0: {} @@ -17821,6 +17938,10 @@ snapshots: optionalDependencies: '@types/react': 18.3.5 + use-memo-one@1.1.3(react@18.3.1): + dependencies: + react: 18.3.1 + use-sidecar@1.1.2(@types/react@18.3.5)(react@18.3.1): dependencies: detect-node-es: 1.1.0