Skip to content

Commit 229bc64

Browse files
Merge pull request #90 from commitd/sh/layout
feat: add a flag to layout on change
2 parents ead751b + aeac4f2 commit 229bc64

File tree

12 files changed

+154
-12
lines changed

12 files changed

+154
-12
lines changed

apps/docs/src/Graph.stories.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { Box, Column } from '@committed/components'
2+
import { cytoscapeRenderer, Graph, GraphDebugControl } from '@committed/components-graph'
23
import { Meta } from '@storybook/react'
34
import React, { useState } from 'react'
4-
import { Graph, cytoscapeRenderer, GraphDebugControl,} from '@committed/components-graph'
55
import { exampleModel } from './StoryUtil'
66

77
export default {

apps/docs/src/GraphToolbar.stories.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ function isFunction(
1616
const empty = {
1717
zoom: false,
1818
layout: false,
19+
relayout: false,
1920
refit: false,
2021
hide: false,
2122
size: false,

package-lock.json

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/graph/src/graph/LayoutModel.test.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,3 +43,8 @@ it('Validating an validated layout does nothing', () => {
4343
const model = layoutModel.setLayout('grid').validate()
4444
expect(model.validate()).toBe(model)
4545
})
46+
47+
it('Changing the onChange does not invalidate the layout', () => {
48+
expect(layoutModel.setOnChange(false).isDirty()).toBe(false)
49+
expect(layoutModel.setOnChange(true).isDirty()).toBe(false)
50+
})

packages/graph/src/graph/LayoutModel.ts

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,16 @@ import { CustomGraphLayout, GraphLayout, PresetGraphLayout } from './types'
22

33
export class LayoutModel {
44
private readonly layout: GraphLayout
5+
private readonly onChange: boolean
56
private readonly invalidated: boolean
67

78
static createDefault(): LayoutModel {
8-
return new LayoutModel('force-directed', true)
9+
return new LayoutModel('force-directed', true, true)
910
}
1011

11-
constructor(layout: GraphLayout, invalidated = true) {
12+
constructor(layout: GraphLayout, onChange = true, invalidated = true) {
1213
this.layout = layout
14+
this.onChange = onChange
1315
this.invalidated = invalidated
1416
}
1517

@@ -21,24 +23,32 @@ export class LayoutModel {
2123
return this.invalidated
2224
}
2325

26+
isOnChange(): boolean {
27+
return this.onChange
28+
}
29+
2430
validate(): LayoutModel {
2531
if (!this.invalidated) {
2632
return this
2733
} else {
28-
return new LayoutModel(this.layout, false)
34+
return new LayoutModel(this.layout, this.onChange, false)
2935
}
3036
}
3137

3238
invalidate(): LayoutModel {
3339
if (this.invalidated) {
3440
return this
3541
} else {
36-
return new LayoutModel(this.layout, true)
42+
return new LayoutModel(this.layout, this.onChange, true)
3743
}
3844
}
3945

4046
setLayout(layout: GraphLayout): LayoutModel {
41-
return new LayoutModel(layout, true)
47+
return new LayoutModel(layout, this.onChange, true)
48+
}
49+
50+
setOnChange(onChange: boolean): LayoutModel {
51+
return new LayoutModel(this.layout, onChange, false)
4252
}
4353

4454
/**

packages/react/src/components/GraphDebugControl/GraphDebugControl.tsx

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,14 @@
1-
import { Box, Button, Flex, Select, SelectItem } from '@committed/components'
1+
import {
2+
Box,
3+
Button,
4+
Checkbox,
5+
Flex,
6+
Select,
7+
SelectItem,
8+
} from '@committed/components'
29
import { Generator, GraphModel, PresetGraphLayout } from '@committed/graph'
3-
import { defaultLayouts } from '../../graph'
410
import React from 'react'
11+
import { defaultLayouts } from '../../graph'
512

613
const { addRandomNode, addRandomEdge, removeRandomNode, removeRandomEdge } =
714
Generator
@@ -44,6 +51,18 @@ export const GraphDebugControl: React.FC<GraphDebugControlProps> = ({
4451
<Button onClick={() => onChange(removeRandomEdge(model))}>
4552
Remove Random Edge
4653
</Button>
54+
<Checkbox
55+
label="Layout on change"
56+
checked={model.getCurrentLayout().isOnChange()}
57+
onCheckedChange={(checked: boolean) =>
58+
onChange(
59+
GraphModel.applyLayout(
60+
model,
61+
model.getCurrentLayout().setOnChange(checked)
62+
)
63+
)
64+
}
65+
/>
4766
<Button
4867
onClick={() =>
4968
onChange(

packages/react/src/components/GraphToolbar/GraphToolbar.test.tsx

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import { Graph } from '../Graph'
1414
const empty = {
1515
zoom: false,
1616
layout: false,
17+
relayout: false,
1718
refit: false,
1819
hide: false,
1920
size: false,
@@ -108,6 +109,10 @@ export const Layout: React.FC<TemplateProps> = (props: TemplateProps) => (
108109
<Template {...empty} layout {...props} />
109110
)
110111

112+
export const Relayout: React.FC<TemplateProps> = (props: TemplateProps) => (
113+
<Template {...empty} relayout {...props} />
114+
)
115+
111116
export const SizeBy: React.FC<TemplateProps> = (props: TemplateProps) => (
112117
<Template {...empty} size {...props} />
113118
)

packages/react/src/components/GraphToolbar/GraphToolbar.tsx

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import { GraphLayoutOptions } from './GraphLayoutOptions'
1717
import { GraphLayoutRun } from './GraphLayoutRun'
1818
import { Hide } from './Hide'
1919
import { Refit } from './Refit'
20+
import { ReLayout } from './ReLayout'
2021
import { SizeBy } from './SizeBy'
2122
import { Zoom } from './Zoom'
2223

@@ -76,6 +77,7 @@ export type GraphToolbarProps = CSSProps &
7677
layouts?: GraphLayout[]
7778
zoom?: boolean
7879
layout?: boolean
80+
relayout?: boolean
7981
refit?: boolean
8082
hide?: boolean
8183
size?: boolean
@@ -96,11 +98,13 @@ export const GraphToolbar: React.FC<GraphToolbarProps> = ({
9698
layouts = [],
9799
zoom = true,
98100
layout = true,
101+
relayout = true,
99102
refit = true,
100103
hide = true,
101104
size = true,
102105
buttonVariant = 'tertiary',
103106
css,
107+
children,
104108
...props
105109
}) => {
106110
const menuItems = useMemo(() => {
@@ -118,6 +122,12 @@ export const GraphToolbar: React.FC<GraphToolbarProps> = ({
118122
)
119123
}
120124

125+
if (relayout) {
126+
items.push(
127+
<ReLayout key="relayout" model={model} onModelChange={onModelChange} />
128+
)
129+
}
130+
121131
if (layout && layouts.length > 0) {
122132
items.push(
123133
<GraphLayoutOptions
@@ -129,7 +139,7 @@ export const GraphToolbar: React.FC<GraphToolbarProps> = ({
129139
)
130140
}
131141
return items.filter((item) => item !== null)
132-
}, [hide, layout, layouts, model, onModelChange, size])
142+
}, [hide, layout, layouts, model, onModelChange, size, relayout])
133143

134144
return (
135145
<StyledToolbar css={css as any} {...props}>
@@ -157,6 +167,7 @@ export const GraphToolbar: React.FC<GraphToolbarProps> = ({
157167
onModelChange={onModelChange}
158168
/>
159169
)}
170+
{children}
160171
{menuItems.length > 0 && (
161172
<Menu>
162173
<MenuTrigger>
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import React from 'react'
2+
import { renderLight, screen, userEvent } from '../../test/setup'
3+
import { Relayout } from './GraphToolbar.test'
4+
5+
it('Can select to size by', () => {
6+
renderLight(<Relayout withGraph={false} />)
7+
userEvent.tab()
8+
userEvent.keyboard('{enter}')
9+
expect(
10+
screen
11+
.getByRole('menuitemcheckbox', { name: /Layout on change/i })
12+
.getAttribute('aria-checked')
13+
).toBe('true')
14+
userEvent.click(
15+
screen.getByRole('menuitemcheckbox', { name: /Layout on change/i })
16+
)
17+
userEvent.tab()
18+
userEvent.keyboard('{enter}')
19+
expect(
20+
screen
21+
.getByRole('menuitemcheckbox', { name: /Layout on change/i })
22+
.getAttribute('aria-checked')
23+
).toBe('false')
24+
})
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
/* eslint-disable @typescript-eslint/no-explicit-any */
2+
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
3+
import { CSSProps, MenuCheckboxItem, VariantProps } from '@committed/components'
4+
import { GraphModel } from '@committed/graph'
5+
import React, { useCallback } from 'react'
6+
7+
export type ReLayoutProps = CSSProps &
8+
VariantProps<typeof MenuCheckboxItem> & {
9+
/** Declarative definition of graph state */
10+
model: GraphModel
11+
/** The graph model change callback */
12+
onModelChange: (
13+
model: GraphModel | ((model2: GraphModel) => GraphModel)
14+
) => void
15+
}
16+
17+
/**
18+
* A GraphToolbar sub-component to relayout on graph change
19+
*/
20+
export const ReLayout: React.FC<ReLayoutProps> = ({
21+
model,
22+
onModelChange,
23+
css,
24+
...props
25+
}) => {
26+
const handleToggle = useCallback((): void => {
27+
onModelChange(
28+
GraphModel.applyLayout(
29+
model,
30+
model
31+
.getCurrentLayout()
32+
.setOnChange(!model.getCurrentLayout().isOnChange())
33+
)
34+
)
35+
}, [model, onModelChange])
36+
37+
return (
38+
<>
39+
<MenuCheckboxItem
40+
css={css as any}
41+
{...props}
42+
key="hideEdgeLabels"
43+
checked={model.getCurrentLayout().isOnChange()}
44+
onCheckedChange={handleToggle}
45+
>
46+
Layout on change
47+
</MenuCheckboxItem>
48+
</>
49+
)
50+
}

0 commit comments

Comments
 (0)