Skip to content

Commit a1d1c5f

Browse files
create-issue-branch[bot]loiswells97patch0
authored
Set up split toggle console UI (#234)
closes #233 Co-authored-by: loiswells97 <[email protected]> Co-authored-by: Lois Wells <[email protected]> Co-authored-by: Lois Wells <[email protected]> Co-authored-by: Patrick Cherry <[email protected]>
1 parent c97b093 commit a1d1c5f

File tree

12 files changed

+464
-45
lines changed

12 files changed

+464
-45
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
1414
- Cookie banner on the editor site (#206) but not in the embedded viewer (#231)
1515
- Unit tests for login button and 'useProject' hook (#211)
1616
- Script for Google Tag Manager to be used on the standalone editor site (#225)
17+
- Ability to switch between split and tabbed output views on the editor site and in the web component (#234)
1718
- Indentation markers in the editor (#237)
1819

1920
### Changed

src/Icons.js

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -212,6 +212,16 @@ export const ShareIcon = () => {
212212
)
213213
}
214214

215+
export const SplitViewIcon = () => {
216+
const [cookies] = useCookies(['fontSize'])
217+
const scale = (fontScaleFactors[cookies.fontSize] || 1) * 1.2
218+
return (
219+
<svg transform={`scale(${scale}, ${scale})`} width="14" height="10" viewBox="0 0 14 10" fill="none" xmlns="http://www.w3.org/2000/svg">
220+
<path fillRule="evenodd" clipRule="evenodd" d="M0.919038 0.918794C0.543966 1.29387 0.333252 1.80257 0.333252 2.33301V7.66634C0.333252 8.19677 0.543966 8.70548 0.919038 9.08055C1.29411 9.45563 1.80282 9.66634 2.33325 9.66634H11.6666C12.197 9.66634 12.7057 9.45563 13.0808 9.08055C13.4559 8.70548 13.6666 8.19677 13.6666 7.66634V2.33301C13.6666 1.80257 13.4559 1.29387 13.0808 0.918794C12.7057 0.543722 12.197 0.333008 11.6666 0.333008H2.33325C1.80282 0.333008 1.29411 0.543722 0.919038 0.918794ZM11.6666 1.66634H2.33325C2.15644 1.66634 1.98687 1.73658 1.86185 1.8616C1.73682 1.98663 1.66659 2.1562 1.66659 2.33301V4.33301H12.3333V2.33301C12.3333 2.1562 12.263 1.98663 12.138 1.8616C12.013 1.73658 11.8434 1.66634 11.6666 1.66634ZM1.66659 7.66634V5.66634H4.33325V5.66602H6.33325V5.66634H12.3333V7.66634C12.3333 7.84315 12.263 8.01272 12.138 8.13775C12.013 8.26277 11.8434 8.33301 11.6666 8.33301H5.66658V8.33268H4.33347L4.33325 8.33301H2.33325C2.15644 8.33301 1.98687 8.26277 1.86185 8.13775C1.73682 8.01272 1.66659 7.84315 1.66659 7.66634Z"/>
221+
</svg>
222+
)
223+
}
224+
215225
export const SquaresIcon = () => {
216226
const [cookies] = useCookies(['fontSize'])
217227
const scale = fontScaleFactors[cookies.fontSize] || 1
@@ -242,6 +252,16 @@ export const SunIcon = () => {
242252
)
243253
}
244254

255+
export const TabbedViewIcon = () => {
256+
const [cookies] = useCookies(['fontSize'])
257+
const scale = (fontScaleFactors[cookies.fontSize] || 1) * 1.2
258+
return (
259+
<svg transform={`scale(${scale}, ${scale})`} width="14" height="10" viewBox="0 0 14 10" fill="none" xmlns="http://www.w3.org/2000/svg">
260+
<path fillRule="evenodd" clipRule="evenodd" d="M0.333252 2.33301C0.333252 1.80257 0.543966 1.29387 0.919038 0.918794C1.29411 0.543722 1.80282 0.333008 2.33325 0.333008H11.6666C12.197 0.333008 12.7057 0.543722 13.0808 0.918794C13.4559 1.29387 13.6666 1.80257 13.6666 2.33301V7.66634C13.6666 8.19677 13.4559 8.70548 13.0808 9.08055C12.7057 9.45563 12.197 9.66634 11.6666 9.66634H2.33325C1.80282 9.66634 1.29411 9.45563 0.919038 9.08055C0.543966 8.70548 0.333252 8.19677 0.333252 7.66634V2.33301ZM7.66658 1.66634H11.6666C11.8434 1.66634 12.013 1.73658 12.138 1.8616C12.263 1.98663 12.3333 2.1562 12.3333 2.33301V7.66634C12.3333 7.84315 12.263 8.01272 12.138 8.13775C12.013 8.26277 11.8434 8.33301 11.6666 8.33301H7.66658V1.66634ZM6.33325 1.66634H2.33325C2.15644 1.66634 1.98687 1.73658 1.86185 1.8616C1.73682 1.98663 1.66659 2.1562 1.66659 2.33301V7.66634C1.66659 7.84315 1.73682 8.01272 1.86185 8.13775C1.98687 8.26277 2.15644 8.33301 2.33325 8.33301H6.33325V1.66634Z"/>
261+
</svg>
262+
)
263+
}
264+
245265
export const TemperatureIcon = () => {
246266
const [cookies] = useCookies(['fontSize'])
247267
const scale = (fontScaleFactors[cookies.fontSize] || 1) * 1.6

src/components/Editor/EditorSlice.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ export const EditorSlice = createSlice({
1010
codeRunTriggered: false,
1111
drawTriggered: false,
1212
isEmbedded: false,
13+
isSplitView: true,
1314
codeRunStopped: false,
1415
projectList: [],
1516
projectListLoaded: false,
@@ -28,6 +29,9 @@ export const EditorSlice = createSlice({
2829
setEmbedded: (state, _action) => {
2930
state.isEmbedded = true;
3031
},
32+
setIsSplitView: (state, action) => {
33+
state.isSplitView = action.payload;
34+
},
3135
setNameError: (state, action) => {
3236
state.nameError = action.payload;
3337
},
@@ -104,6 +108,7 @@ export const {
104108
codeRunHandled,
105109
setEmbedded,
106110
setError,
111+
setIsSplitView,
107112
setNameError,
108113
setProject,
109114
setProjectList,
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import React from "react";
2+
import { useDispatch, useSelector } from "react-redux";
3+
import { SplitViewIcon, TabbedViewIcon } from "../../../../Icons";
4+
import { setIsSplitView } from "../../EditorSlice";
5+
6+
import './OutputViewToggle.scss';
7+
8+
const OutputViewToggle = () => {
9+
10+
const isSplitView = useSelector((state) => state.editor.isSplitView)
11+
const codeRunTriggered = useSelector((state) => state.editor.codeRunTriggered)
12+
const drawTriggered = useSelector((state) => state.editor.drawTriggered)
13+
const dispatch = useDispatch()
14+
15+
const switchToTabbedView = () => {
16+
dispatch(setIsSplitView(false))
17+
}
18+
19+
const switchToSplitView = () => {
20+
dispatch(setIsSplitView(true))
21+
}
22+
23+
return (
24+
<div className = {`output-view-toggle`} disabled = {codeRunTriggered || drawTriggered}>
25+
<button className = {`output-view-toggle__button output-view-toggle__button--tabbed${isSplitView ? "" : " output-view-toggle__button--active"}` } disabled = {codeRunTriggered || drawTriggered} onClick={switchToTabbedView}>
26+
<TabbedViewIcon />
27+
</button>
28+
<button className = {`output-view-toggle__button output-view-toggle__button--split${isSplitView ? " output-view-toggle__button--active" : ""}`} disabled = {codeRunTriggered || drawTriggered} onClick={switchToSplitView}>
29+
<SplitViewIcon />
30+
</button>
31+
</div>
32+
)
33+
}
34+
35+
export default OutputViewToggle
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
@import '../../../../colours.scss';
2+
3+
.output-view-toggle {
4+
display: inline-flex;
5+
margin: auto var(--spacing-1) auto auto;
6+
align-items: center;
7+
border-radius: 5px;
8+
9+
&__button {
10+
padding: var(--spacing-half) var(--spacing-1) var(--spacing-half) var(--spacing-1);
11+
cursor: pointer;
12+
13+
&:disabled {
14+
cursor: inherit;
15+
}
16+
}
17+
}
18+
19+
.--light {
20+
.output-view-toggle {
21+
background-color: $editor-light-light-grey;
22+
&__button {
23+
svg {
24+
fill: $editor-mid-grey;
25+
}
26+
&--active {
27+
background-color: $editor-light-grey;
28+
border-radius: 5px;
29+
svg {
30+
fill: $editor-black;
31+
}
32+
}
33+
}
34+
}
35+
}
36+
37+
.--dark {
38+
.output-view-toggle {
39+
background-color: $editor-dark-dark;
40+
&__button {
41+
svg {
42+
fill: $editor-mid-grey;
43+
}
44+
&--active {
45+
background-color: $editor-grey;
46+
border-radius: 5px;
47+
svg {
48+
fill: $editor-white;
49+
}
50+
}
51+
&:disabled {
52+
svg {
53+
fill: $editor-grey;
54+
}
55+
}
56+
&--active:disabled {
57+
svg {
58+
fill: $editor-mid-grey;
59+
}
60+
}
61+
}
62+
}
63+
}
64+
65+
.--light .output-view-toggle__button:disabled {
66+
svg {
67+
fill: $editor-mid-light-grey;
68+
}
69+
&.output-view-toggle__button--active {
70+
svg {
71+
fill: $editor-mid-grey;
72+
}
73+
}
74+
}
Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
import React from "react";
2+
import { fireEvent, render, screen } from "@testing-library/react"
3+
import { Provider } from 'react-redux';
4+
import configureStore from 'redux-mock-store';
5+
import OutputViewToggle from "./OutputViewToggle";
6+
import { setIsSplitView } from "../../EditorSlice";
7+
8+
describe('When in tabbed view', () => {
9+
beforeEach(() => {
10+
const middlewares = []
11+
const mockStore = configureStore(middlewares)
12+
const initialState = {
13+
editor: {
14+
isSplitView: false,
15+
}
16+
}
17+
const store = mockStore(initialState);
18+
render(<Provider store={store}><OutputViewToggle /></Provider>);
19+
})
20+
21+
test('Tabbed view button is active', () => {
22+
expect(screen.getAllByRole('button')[0]).toHaveClass('output-view-toggle__button--tabbed output-view-toggle__button--active')
23+
})
24+
25+
test('Split view button is not active', () => {
26+
expect(screen.getAllByRole('button')[1]).toHaveClass('output-view-toggle__button--split')
27+
expect(screen.getAllByRole('button')[1]).not.toHaveClass('output-view-toggle__button--active')
28+
})
29+
})
30+
31+
describe('When in split view', () => {
32+
beforeEach(() => {
33+
const middlewares = []
34+
const mockStore = configureStore(middlewares)
35+
const initialState = {
36+
editor: {
37+
isSplitView: true,
38+
}
39+
}
40+
const store = mockStore(initialState);
41+
render(<Provider store={store}><OutputViewToggle /></Provider>);
42+
})
43+
44+
test('Split view button is active', () => {
45+
expect(screen.getAllByRole('button')[1]).toHaveClass('output-view-toggle__button--split output-view-toggle__button--active')
46+
})
47+
48+
test('Tabbed view button is not active', () => {
49+
expect(screen.getAllByRole('button')[0]).toHaveClass('output-view-toggle__button--tabbed')
50+
expect(screen.getAllByRole('button')[0]).not.toHaveClass('output-view-toggle__button--active')
51+
})
52+
})
53+
54+
test('Clicking tabbed view icon switches to tabbed view', () => {
55+
const middlewares = []
56+
const mockStore = configureStore(middlewares)
57+
const initialState = {
58+
editor: {}
59+
}
60+
const store = mockStore(initialState);
61+
render(<Provider store={store}><OutputViewToggle /></Provider>);
62+
fireEvent.click(screen.getAllByRole('button')[0])
63+
const expectedActions = [setIsSplitView(false)]
64+
expect(store.getActions()).toEqual(expectedActions);
65+
})
66+
67+
test('Clicking split view icon switches to tabbed view', () => {
68+
const middlewares = []
69+
const mockStore = configureStore(middlewares)
70+
const initialState = {
71+
editor: {}
72+
}
73+
const store = mockStore(initialState);
74+
render(<Provider store={store}><OutputViewToggle /></Provider>);
75+
fireEvent.click(screen.getAllByRole('button')[1])
76+
const expectedActions = [setIsSplitView(true)]
77+
expect(store.getActions()).toEqual(expectedActions);
78+
})
79+
80+
describe('When in a code run is triggered', () => {
81+
beforeEach(() => {
82+
const middlewares = []
83+
const mockStore = configureStore(middlewares)
84+
const initialState = {
85+
editor: {
86+
codeRunTriggered: true,
87+
}
88+
}
89+
const store = mockStore(initialState);
90+
render(<Provider store={store}><OutputViewToggle /></Provider>);
91+
})
92+
93+
test('Tabbed view button is disabled', () => {
94+
expect(screen.getAllByRole('button')[0]).toHaveClass('output-view-toggle__button--tabbed')
95+
expect(screen.getAllByRole('button')[0]).toBeDisabled()
96+
})
97+
98+
test('Split view button is disabled', () => {
99+
expect(screen.getAllByRole('button')[1]).toHaveClass('output-view-toggle__button--split')
100+
expect(screen.getAllByRole('button')[1]).toBeDisabled()
101+
})
102+
})
103+
104+
describe('When in a draw run is triggered', () => {
105+
beforeEach(() => {
106+
const middlewares = []
107+
const mockStore = configureStore(middlewares)
108+
const initialState = {
109+
editor: {
110+
drawTriggered: true,
111+
}
112+
}
113+
const store = mockStore(initialState);
114+
render(<Provider store={store}><OutputViewToggle /></Provider>);
115+
})
116+
117+
test('Tabbed view button is disabled', () => {
118+
expect(screen.getAllByRole('button')[0]).toHaveClass('output-view-toggle__button--tabbed')
119+
expect(screen.getAllByRole('button')[0]).toBeDisabled()
120+
})
121+
122+
test('Split view button is disabled', () => {
123+
expect(screen.getAllByRole('button')[1]).toHaveClass('output-view-toggle__button--split')
124+
expect(screen.getAllByRole('button')[1]).toBeDisabled()
125+
})
126+
})
127+
128+
describe('When in neither a code run nor a draw run is triggered', () => {
129+
beforeEach(() => {
130+
const middlewares = []
131+
const mockStore = configureStore(middlewares)
132+
const initialState = {
133+
editor: {
134+
codeRunTriggered:false,
135+
drawTriggered: false,
136+
}
137+
}
138+
const store = mockStore(initialState);
139+
render(<Provider store={store}><OutputViewToggle /></Provider>);
140+
})
141+
142+
test('Tabbed view button is enabled', () => {
143+
expect(screen.getAllByRole('button')[0]).toHaveClass('output-view-toggle__button--tabbed')
144+
expect(screen.getAllByRole('button')[0]).not.toBeDisabled()
145+
})
146+
147+
test('Split view button is enabled', () => {
148+
expect(screen.getAllByRole('button')[1]).toHaveClass('output-view-toggle__button--split')
149+
expect(screen.getAllByRole('button')[1]).not.toBeDisabled()
150+
})
151+
})

0 commit comments

Comments
 (0)