Skip to content

Commit

Permalink
Set up split toggle console UI (#234)
Browse files Browse the repository at this point in the history
closes #233

Co-authored-by: loiswells97 <loiswells97@users.noreply.github.com>
Co-authored-by: Lois Wells <lois.wells@raspberrypi.org>
Co-authored-by: Lois Wells <88904316+loiswells97@users.noreply.github.com>
Co-authored-by: Patrick Cherry <patch0@users.noreply.github.com>
  • Loading branch information
5 people committed Oct 21, 2022
1 parent c97b093 commit a1d1c5f
Show file tree
Hide file tree
Showing 12 changed files with 464 additions and 45 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
- Cookie banner on the editor site (#206) but not in the embedded viewer (#231)
- Unit tests for login button and 'useProject' hook (#211)
- Script for Google Tag Manager to be used on the standalone editor site (#225)
- Ability to switch between split and tabbed output views on the editor site and in the web component (#234)
- Indentation markers in the editor (#237)

### Changed
Expand Down
20 changes: 20 additions & 0 deletions src/Icons.js
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,16 @@ export const ShareIcon = () => {
)
}

export const SplitViewIcon = () => {
const [cookies] = useCookies(['fontSize'])
const scale = (fontScaleFactors[cookies.fontSize] || 1) * 1.2
return (
<svg transform={`scale(${scale}, ${scale})`} width="14" height="10" viewBox="0 0 14 10" fill="none" xmlns="http://www.w3.org/2000/svg">
<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"/>
</svg>
)
}

export const SquaresIcon = () => {
const [cookies] = useCookies(['fontSize'])
const scale = fontScaleFactors[cookies.fontSize] || 1
Expand Down Expand Up @@ -242,6 +252,16 @@ export const SunIcon = () => {
)
}

export const TabbedViewIcon = () => {
const [cookies] = useCookies(['fontSize'])
const scale = (fontScaleFactors[cookies.fontSize] || 1) * 1.2
return (
<svg transform={`scale(${scale}, ${scale})`} width="14" height="10" viewBox="0 0 14 10" fill="none" xmlns="http://www.w3.org/2000/svg">
<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"/>
</svg>
)
}

export const TemperatureIcon = () => {
const [cookies] = useCookies(['fontSize'])
const scale = (fontScaleFactors[cookies.fontSize] || 1) * 1.6
Expand Down
5 changes: 5 additions & 0 deletions src/components/Editor/EditorSlice.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ export const EditorSlice = createSlice({
codeRunTriggered: false,
drawTriggered: false,
isEmbedded: false,
isSplitView: true,
codeRunStopped: false,
projectList: [],
projectListLoaded: false,
Expand All @@ -28,6 +29,9 @@ export const EditorSlice = createSlice({
setEmbedded: (state, _action) => {
state.isEmbedded = true;
},
setIsSplitView: (state, action) => {
state.isSplitView = action.payload;
},
setNameError: (state, action) => {
state.nameError = action.payload;
},
Expand Down Expand Up @@ -104,6 +108,7 @@ export const {
codeRunHandled,
setEmbedded,
setError,
setIsSplitView,
setNameError,
setProject,
setProjectList,
Expand Down
35 changes: 35 additions & 0 deletions src/components/Editor/Runners/PythonRunner/OutputViewToggle.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import React from "react";
import { useDispatch, useSelector } from "react-redux";
import { SplitViewIcon, TabbedViewIcon } from "../../../../Icons";
import { setIsSplitView } from "../../EditorSlice";

import './OutputViewToggle.scss';

const OutputViewToggle = () => {

const isSplitView = useSelector((state) => state.editor.isSplitView)
const codeRunTriggered = useSelector((state) => state.editor.codeRunTriggered)
const drawTriggered = useSelector((state) => state.editor.drawTriggered)
const dispatch = useDispatch()

const switchToTabbedView = () => {
dispatch(setIsSplitView(false))
}

const switchToSplitView = () => {
dispatch(setIsSplitView(true))
}

return (
<div className = {`output-view-toggle`} disabled = {codeRunTriggered || drawTriggered}>
<button className = {`output-view-toggle__button output-view-toggle__button--tabbed${isSplitView ? "" : " output-view-toggle__button--active"}` } disabled = {codeRunTriggered || drawTriggered} onClick={switchToTabbedView}>
<TabbedViewIcon />
</button>
<button className = {`output-view-toggle__button output-view-toggle__button--split${isSplitView ? " output-view-toggle__button--active" : ""}`} disabled = {codeRunTriggered || drawTriggered} onClick={switchToSplitView}>
<SplitViewIcon />
</button>
</div>
)
}

export default OutputViewToggle
74 changes: 74 additions & 0 deletions src/components/Editor/Runners/PythonRunner/OutputViewToggle.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
@import '../../../../colours.scss';

.output-view-toggle {
display: inline-flex;
margin: auto var(--spacing-1) auto auto;
align-items: center;
border-radius: 5px;

&__button {
padding: var(--spacing-half) var(--spacing-1) var(--spacing-half) var(--spacing-1);
cursor: pointer;

&:disabled {
cursor: inherit;
}
}
}

.--light {
.output-view-toggle {
background-color: $editor-light-light-grey;
&__button {
svg {
fill: $editor-mid-grey;
}
&--active {
background-color: $editor-light-grey;
border-radius: 5px;
svg {
fill: $editor-black;
}
}
}
}
}

.--dark {
.output-view-toggle {
background-color: $editor-dark-dark;
&__button {
svg {
fill: $editor-mid-grey;
}
&--active {
background-color: $editor-grey;
border-radius: 5px;
svg {
fill: $editor-white;
}
}
&:disabled {
svg {
fill: $editor-grey;
}
}
&--active:disabled {
svg {
fill: $editor-mid-grey;
}
}
}
}
}

.--light .output-view-toggle__button:disabled {
svg {
fill: $editor-mid-light-grey;
}
&.output-view-toggle__button--active {
svg {
fill: $editor-mid-grey;
}
}
}
151 changes: 151 additions & 0 deletions src/components/Editor/Runners/PythonRunner/OutputViewToggle.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
import React from "react";
import { fireEvent, render, screen } from "@testing-library/react"
import { Provider } from 'react-redux';
import configureStore from 'redux-mock-store';
import OutputViewToggle from "./OutputViewToggle";
import { setIsSplitView } from "../../EditorSlice";

describe('When in tabbed view', () => {
beforeEach(() => {
const middlewares = []
const mockStore = configureStore(middlewares)
const initialState = {
editor: {
isSplitView: false,
}
}
const store = mockStore(initialState);
render(<Provider store={store}><OutputViewToggle /></Provider>);
})

test('Tabbed view button is active', () => {
expect(screen.getAllByRole('button')[0]).toHaveClass('output-view-toggle__button--tabbed output-view-toggle__button--active')
})

test('Split view button is not active', () => {
expect(screen.getAllByRole('button')[1]).toHaveClass('output-view-toggle__button--split')
expect(screen.getAllByRole('button')[1]).not.toHaveClass('output-view-toggle__button--active')
})
})

describe('When in split view', () => {
beforeEach(() => {
const middlewares = []
const mockStore = configureStore(middlewares)
const initialState = {
editor: {
isSplitView: true,
}
}
const store = mockStore(initialState);
render(<Provider store={store}><OutputViewToggle /></Provider>);
})

test('Split view button is active', () => {
expect(screen.getAllByRole('button')[1]).toHaveClass('output-view-toggle__button--split output-view-toggle__button--active')
})

test('Tabbed view button is not active', () => {
expect(screen.getAllByRole('button')[0]).toHaveClass('output-view-toggle__button--tabbed')
expect(screen.getAllByRole('button')[0]).not.toHaveClass('output-view-toggle__button--active')
})
})

test('Clicking tabbed view icon switches to tabbed view', () => {
const middlewares = []
const mockStore = configureStore(middlewares)
const initialState = {
editor: {}
}
const store = mockStore(initialState);
render(<Provider store={store}><OutputViewToggle /></Provider>);
fireEvent.click(screen.getAllByRole('button')[0])
const expectedActions = [setIsSplitView(false)]
expect(store.getActions()).toEqual(expectedActions);
})

test('Clicking split view icon switches to tabbed view', () => {
const middlewares = []
const mockStore = configureStore(middlewares)
const initialState = {
editor: {}
}
const store = mockStore(initialState);
render(<Provider store={store}><OutputViewToggle /></Provider>);
fireEvent.click(screen.getAllByRole('button')[1])
const expectedActions = [setIsSplitView(true)]
expect(store.getActions()).toEqual(expectedActions);
})

describe('When in a code run is triggered', () => {
beforeEach(() => {
const middlewares = []
const mockStore = configureStore(middlewares)
const initialState = {
editor: {
codeRunTriggered: true,
}
}
const store = mockStore(initialState);
render(<Provider store={store}><OutputViewToggle /></Provider>);
})

test('Tabbed view button is disabled', () => {
expect(screen.getAllByRole('button')[0]).toHaveClass('output-view-toggle__button--tabbed')
expect(screen.getAllByRole('button')[0]).toBeDisabled()
})

test('Split view button is disabled', () => {
expect(screen.getAllByRole('button')[1]).toHaveClass('output-view-toggle__button--split')
expect(screen.getAllByRole('button')[1]).toBeDisabled()
})
})

describe('When in a draw run is triggered', () => {
beforeEach(() => {
const middlewares = []
const mockStore = configureStore(middlewares)
const initialState = {
editor: {
drawTriggered: true,
}
}
const store = mockStore(initialState);
render(<Provider store={store}><OutputViewToggle /></Provider>);
})

test('Tabbed view button is disabled', () => {
expect(screen.getAllByRole('button')[0]).toHaveClass('output-view-toggle__button--tabbed')
expect(screen.getAllByRole('button')[0]).toBeDisabled()
})

test('Split view button is disabled', () => {
expect(screen.getAllByRole('button')[1]).toHaveClass('output-view-toggle__button--split')
expect(screen.getAllByRole('button')[1]).toBeDisabled()
})
})

describe('When in neither a code run nor a draw run is triggered', () => {
beforeEach(() => {
const middlewares = []
const mockStore = configureStore(middlewares)
const initialState = {
editor: {
codeRunTriggered:false,
drawTriggered: false,
}
}
const store = mockStore(initialState);
render(<Provider store={store}><OutputViewToggle /></Provider>);
})

test('Tabbed view button is enabled', () => {
expect(screen.getAllByRole('button')[0]).toHaveClass('output-view-toggle__button--tabbed')
expect(screen.getAllByRole('button')[0]).not.toBeDisabled()
})

test('Split view button is enabled', () => {
expect(screen.getAllByRole('button')[1]).toHaveClass('output-view-toggle__button--split')
expect(screen.getAllByRole('button')[1]).not.toBeDisabled()
})
})
Loading

0 comments on commit a1d1c5f

Please sign in to comment.