fix: simplify mobile layout tabs (#44431)
* fix: simplify mobile layout tabs Co-authored-by: Oliver Eyton-Williams <ojeytonwilliams@gmail.com> Co-authored-by: Oliver Eyton-Williams <ojeytonwilliams@gmail.com>
This commit is contained in:
@ -80,6 +80,7 @@ exports.createPages = function createPages({ graphql, actions, reporter }) {
|
||||
fields {
|
||||
slug
|
||||
}
|
||||
hasEditableBoundaries
|
||||
id
|
||||
order
|
||||
required {
|
||||
|
@ -139,6 +139,7 @@ export type ChallengeNode = {
|
||||
forumTopicId: number;
|
||||
guideUrl: string;
|
||||
head: string[];
|
||||
hasEditableBoundaries: boolean;
|
||||
helpCategory: string;
|
||||
id: string;
|
||||
instructions: string;
|
||||
|
@ -1,52 +1,55 @@
|
||||
import { TabPane, Tabs } from '@freecodecamp/react-bootstrap';
|
||||
import i18next from 'i18next';
|
||||
import React, { Component, ReactElement } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import { bindActionCreators, Dispatch } from 'redux';
|
||||
import { createStructuredSelector } from 'reselect';
|
||||
import envData from '../../../../../config/env.json';
|
||||
import ToolPanel from '../components/tool-panel';
|
||||
import { currentTabSelector, moveToTab } from '../redux';
|
||||
import EditorTabs from './editor-tabs';
|
||||
|
||||
const { showUpcomingChanges } = envData;
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
currentTab: currentTabSelector as (state: unknown) => number
|
||||
});
|
||||
|
||||
const mapDispatchToProps = (dispatch: Dispatch) =>
|
||||
bindActionCreators(
|
||||
{
|
||||
moveToTab
|
||||
},
|
||||
dispatch
|
||||
);
|
||||
interface MobileLayoutProps {
|
||||
currentTab: number;
|
||||
editor: JSX.Element | null;
|
||||
guideUrl: string;
|
||||
hasEditableBoundaries: boolean;
|
||||
hasNotes: boolean;
|
||||
hasPreview: boolean;
|
||||
instructions: JSX.Element;
|
||||
moveToTab: typeof moveToTab;
|
||||
notes: ReactElement;
|
||||
preview: JSX.Element;
|
||||
testOutput: JSX.Element;
|
||||
videoUrl: string;
|
||||
usesMultifileEditor: boolean;
|
||||
}
|
||||
class MobileLayout extends Component<MobileLayoutProps> {
|
||||
|
||||
enum Tab {
|
||||
Editor = 'editor',
|
||||
Preview = 'preview',
|
||||
Console = 'console',
|
||||
Notes = 'notes',
|
||||
Instructions = 'instructions'
|
||||
}
|
||||
|
||||
interface MobileLayoutState {
|
||||
currentTab: Tab;
|
||||
}
|
||||
|
||||
class MobileLayout extends Component<MobileLayoutProps, MobileLayoutState> {
|
||||
static displayName: string;
|
||||
componentDidMount() {
|
||||
if (this.props.currentTab !== 1) this.props.moveToTab(1);
|
||||
}
|
||||
|
||||
state: MobileLayoutState = {
|
||||
currentTab: this.props.hasEditableBoundaries ? Tab.Editor : Tab.Instructions
|
||||
};
|
||||
|
||||
switchTab = (tab: Tab) => {
|
||||
this.setState({
|
||||
currentTab: tab
|
||||
});
|
||||
};
|
||||
|
||||
render() {
|
||||
const { currentTab } = this.state;
|
||||
const {
|
||||
currentTab,
|
||||
moveToTab,
|
||||
hasEditableBoundaries,
|
||||
instructions,
|
||||
editor,
|
||||
@ -69,26 +72,24 @@ class MobileLayout extends Component<MobileLayoutProps> {
|
||||
// but still needs a way to switch between the different tabs.
|
||||
const projectBasedChallenge = showUpcomingChanges && usesMultifileEditor;
|
||||
|
||||
const eventKeys = [1, 2, 3, 4, 5];
|
||||
|
||||
return (
|
||||
<>
|
||||
<Tabs
|
||||
activeKey={currentTab}
|
||||
defaultActiveKey={1}
|
||||
defaultActiveKey={currentTab}
|
||||
id='challenge-page-tabs'
|
||||
onSelect={moveToTab}
|
||||
onSelect={this.switchTab}
|
||||
>
|
||||
{!hasEditableBoundaries && (
|
||||
<TabPane
|
||||
eventKey={eventKeys.shift()}
|
||||
eventKey={Tab.Instructions}
|
||||
title={i18next.t('learn.editor-tabs.info')}
|
||||
>
|
||||
{instructions}
|
||||
</TabPane>
|
||||
)}
|
||||
<TabPane
|
||||
eventKey={eventKeys.shift()}
|
||||
eventKey={Tab.Editor}
|
||||
title={i18next.t('learn.editor-tabs.code')}
|
||||
{...editorTabPaneProps}
|
||||
>
|
||||
@ -96,7 +97,7 @@ class MobileLayout extends Component<MobileLayoutProps> {
|
||||
{editor}
|
||||
</TabPane>
|
||||
<TabPane
|
||||
eventKey={eventKeys.shift()}
|
||||
eventKey={Tab.Console}
|
||||
title={i18next.t('learn.editor-tabs.tests')}
|
||||
{...editorTabPaneProps}
|
||||
>
|
||||
@ -104,7 +105,7 @@ class MobileLayout extends Component<MobileLayoutProps> {
|
||||
</TabPane>
|
||||
{hasNotes && projectBasedChallenge && (
|
||||
<TabPane
|
||||
eventKey={eventKeys.shift()}
|
||||
eventKey={Tab.Notes}
|
||||
title={i18next.t('learn.editor-tabs.notes')}
|
||||
>
|
||||
{notes}
|
||||
@ -112,7 +113,7 @@ class MobileLayout extends Component<MobileLayoutProps> {
|
||||
)}
|
||||
{hasPreview && (
|
||||
<TabPane
|
||||
eventKey={eventKeys.shift()}
|
||||
eventKey={Tab.Preview}
|
||||
title={i18next.t('learn.editor-tabs.preview')}
|
||||
>
|
||||
{preview}
|
||||
@ -127,4 +128,4 @@ class MobileLayout extends Component<MobileLayoutProps> {
|
||||
|
||||
MobileLayout.displayName = 'MobileLayout';
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(MobileLayout);
|
||||
export default MobileLayout;
|
||||
|
@ -398,20 +398,12 @@ class ShowClassic extends Component<ShowClassicProps, ShowClassicState> {
|
||||
);
|
||||
}
|
||||
|
||||
hasEditableBoundaries() {
|
||||
const { challengeFiles } = this.props;
|
||||
return (
|
||||
challengeFiles?.some(
|
||||
challengeFile => challengeFile.editableRegionBoundaries?.length === 2
|
||||
) ?? false
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
block,
|
||||
fields: { blockName },
|
||||
forumTopicId,
|
||||
hasEditableBoundaries,
|
||||
superBlock,
|
||||
title,
|
||||
usesMultifileEditor,
|
||||
@ -443,7 +435,7 @@ class ShowClassic extends Component<ShowClassicProps, ShowClassicState> {
|
||||
<MobileLayout
|
||||
editor={this.renderEditor()}
|
||||
guideUrl={getGuideUrl({ forumTopicId, title })}
|
||||
hasEditableBoundaries={this.hasEditableBoundaries()}
|
||||
hasEditableBoundaries={hasEditableBoundaries}
|
||||
hasNotes={!!notes}
|
||||
hasPreview={this.hasPreview()}
|
||||
instructions={this.renderInstructionsPanel({
|
||||
@ -461,7 +453,7 @@ class ShowClassic extends Component<ShowClassicProps, ShowClassicState> {
|
||||
block={block}
|
||||
challengeFiles={challengeFiles}
|
||||
editor={this.renderEditor()}
|
||||
hasEditableBoundaries={this.hasEditableBoundaries()}
|
||||
hasEditableBoundaries={hasEditableBoundaries}
|
||||
hasNotes={!!notes}
|
||||
hasPreview={this.hasPreview()}
|
||||
instructions={this.renderInstructionsPanel({
|
||||
@ -504,6 +496,7 @@ export const query = graphql`
|
||||
block
|
||||
title
|
||||
description
|
||||
hasEditableBoundaries
|
||||
instructions
|
||||
notes
|
||||
removeComments
|
||||
|
@ -40,8 +40,6 @@ export const actionTypes = createTypes(
|
||||
'stopResetting',
|
||||
'submitChallenge',
|
||||
|
||||
'moveToTab',
|
||||
|
||||
'setEditorFocusability',
|
||||
'toggleVisibleEditor'
|
||||
],
|
||||
|
@ -120,8 +120,6 @@ export const resetChallenge = createAction(actionTypes.resetChallenge);
|
||||
export const stopResetting = createAction(actionTypes.stopResetting);
|
||||
export const submitChallenge = createAction(actionTypes.submitChallenge);
|
||||
|
||||
export const moveToTab = createAction(actionTypes.moveToTab);
|
||||
|
||||
export const setEditorFocusability = createAction(
|
||||
actionTypes.setEditorFocusability
|
||||
);
|
||||
@ -344,10 +342,6 @@ export const reducer = handleActions(
|
||||
[payload]: true
|
||||
}
|
||||
}),
|
||||
[actionTypes.moveToTab]: (state, { payload }) => ({
|
||||
...state,
|
||||
currentTab: payload
|
||||
}),
|
||||
[actionTypes.executeChallenge]: state => ({
|
||||
...state,
|
||||
currentTab: 3
|
||||
|
@ -2,6 +2,7 @@
|
||||
"name": "Basic JavaScript RPG Game",
|
||||
"isUpcomingChange": true,
|
||||
"usesMultifileEditor": true,
|
||||
"hasEditableBoundaries": true,
|
||||
"dashedName": "basic-javascript-rpg-game",
|
||||
"order": 10,
|
||||
"time": "2 hours",
|
||||
|
@ -2,6 +2,7 @@
|
||||
"name": "D3 Dashboard",
|
||||
"isUpcomingChange": true,
|
||||
"usesMultifileEditor": true,
|
||||
"hasEditableBoundaries": true,
|
||||
"dashedName": "d3-dashboard",
|
||||
"order": 3,
|
||||
"time": "5 hours",
|
||||
|
@ -2,6 +2,7 @@
|
||||
"name": "Functional Programming Spreadsheet",
|
||||
"isUpcomingChange": true,
|
||||
"usesMultifileEditor": true,
|
||||
"hasEditableBoundaries": true,
|
||||
"dashedName": "functional-programming-spreadsheet",
|
||||
"order": 12,
|
||||
"time": "2 hours",
|
||||
|
@ -2,6 +2,7 @@
|
||||
"name": "Intermediate JavaScript Calorie Counter",
|
||||
"isUpcomingChange": true,
|
||||
"usesMultifileEditor": true,
|
||||
"hasEditableBoundaries": true,
|
||||
"dashedName": "intermediate-javascript-calorie-counter",
|
||||
"order": 11,
|
||||
"time": "2 hours",
|
||||
|
@ -2,6 +2,7 @@
|
||||
"name": "Learn Accessibility by Building a Quiz",
|
||||
"isUpcomingChange": true,
|
||||
"usesMultifileEditor": true,
|
||||
"hasEditableBoundaries": true,
|
||||
"dashedName": "learn-accessibility-by-building-a-quiz",
|
||||
"order": 42,
|
||||
"time": "5 hours",
|
||||
|
@ -2,6 +2,7 @@
|
||||
"name": "Learn Basic CSS by Building a Cafe Menu",
|
||||
"isUpcomingChange": true,
|
||||
"usesMultifileEditor": true,
|
||||
"hasEditableBoundaries": true,
|
||||
"dashedName": "learn-basic-css-by-building-a-cafe-menu",
|
||||
"order": 10,
|
||||
"time": "5 hours",
|
||||
|
@ -2,6 +2,7 @@
|
||||
"name": "Learn CSS Animation by Building a Ferris Wheel",
|
||||
"isUpcomingChange": true,
|
||||
"usesMultifileEditor": true,
|
||||
"hasEditableBoundaries": true,
|
||||
"dashedName": "learn-css-animation-by-building-a-ferris-wheel",
|
||||
"order": 15,
|
||||
"time": "5 hours",
|
||||
|
@ -2,6 +2,7 @@
|
||||
"name": "Learn CSS Flexbox by Building a Photo Gallery",
|
||||
"isUpcomingChange": true,
|
||||
"usesMultifileEditor": true,
|
||||
"hasEditableBoundaries": true,
|
||||
"dashedName": "learn-css-flexbox-by-building-a-photo-gallery",
|
||||
"order": 20,
|
||||
"time": "5 hours",
|
||||
|
@ -2,6 +2,7 @@
|
||||
"name": "Learn CSS Grid by Building a Magazine",
|
||||
"isUpcomingChange": true,
|
||||
"usesMultifileEditor": true,
|
||||
"hasEditableBoundaries": true,
|
||||
"dashedName": "learn-css-grid-by-building-a-magazine",
|
||||
"order": 16,
|
||||
"time": "5 hours",
|
||||
|
@ -2,6 +2,7 @@
|
||||
"name": "Learn CSS Variables by Building a City Skyline",
|
||||
"isUpcomingChange": true,
|
||||
"usesMultifileEditor": true,
|
||||
"hasEditableBoundaries": true,
|
||||
"dashedName": "learn-css-variables-by-building-a-city-skyline",
|
||||
"order": 8,
|
||||
"time": "5 hours",
|
||||
|
@ -2,6 +2,7 @@
|
||||
"name": "Learn HTML by Building a Cat Photo App",
|
||||
"isUpcomingChange": true,
|
||||
"usesMultifileEditor": true,
|
||||
"hasEditableBoundaries": true,
|
||||
"dashedName": "learn-html-by-building-a-cat-photo-app",
|
||||
"order": 9,
|
||||
"time": "5 hours",
|
||||
|
@ -2,6 +2,7 @@
|
||||
"name": "Learn HTML Forms by Building a Registration Form",
|
||||
"isUpcomingChange": true,
|
||||
"usesMultifileEditor": true,
|
||||
"hasEditableBoundaries": true,
|
||||
"dashedName": "learn-html-forms-by-building-a-registration-form",
|
||||
"order": 23,
|
||||
"time": "5 hours",
|
||||
|
@ -2,6 +2,7 @@
|
||||
"name": "Learn Intermediate CSS by Building a Picasso Painting",
|
||||
"isUpcomingChange": true,
|
||||
"usesMultifileEditor": true,
|
||||
"hasEditableBoundaries": true,
|
||||
"dashedName": "learn-intermediate-css-by-building-a-picasso-painting",
|
||||
"order": 11,
|
||||
"time": "5 hours",
|
||||
|
@ -2,6 +2,7 @@
|
||||
"name": "Learn Responsive Web Design by Building a Piano",
|
||||
"isUpcomingChange": true,
|
||||
"usesMultifileEditor": true,
|
||||
"hasEditableBoundaries": true,
|
||||
"dashedName": "learn-responsive-web-design-by-building-a-piano",
|
||||
"order": 13,
|
||||
"time": "5 hours",
|
||||
|
@ -2,6 +2,7 @@
|
||||
"name": "Learn the CSS Box Model by Building a Rothko Painting",
|
||||
"isUpcomingChange": true,
|
||||
"usesMultifileEditor": true,
|
||||
"hasEditableBoundaries": true,
|
||||
"dashedName": "learn-the-css-box-model-by-building-a-rothko-painting",
|
||||
"order": 12,
|
||||
"time": "5 hours",
|
||||
|
@ -2,6 +2,7 @@
|
||||
"name": "Learn Typography by Building a Nutrition Label",
|
||||
"isUpcomingChange": true,
|
||||
"usesMultifileEditor": true,
|
||||
"hasEditableBoundaries": true,
|
||||
"dashedName": "learn-typography-by-building-a-nutrition-label",
|
||||
"order": 25,
|
||||
"time": "5 hours",
|
||||
|
@ -296,6 +296,7 @@ ${getFullPath('english')}
|
||||
);
|
||||
const {
|
||||
name: blockName,
|
||||
hasEditableBoundaries,
|
||||
order,
|
||||
isPrivate,
|
||||
required = [],
|
||||
@ -304,6 +305,7 @@ ${getFullPath('english')}
|
||||
usesMultifileEditor
|
||||
} = meta;
|
||||
challenge.block = dasherize(blockName);
|
||||
challenge.hasEditableBoundaries = !!hasEditableBoundaries;
|
||||
challenge.order = order;
|
||||
const superOrder = getSuperOrder(superBlock);
|
||||
if (superOrder !== null) challenge.superOrder = superOrder;
|
||||
|
@ -39,6 +39,7 @@ const schema = Joi.object()
|
||||
}),
|
||||
challengeFiles: Joi.array().items(fileJoi),
|
||||
guideUrl: Joi.string().uri({ scheme: 'https' }),
|
||||
hasEditableBoundaries: Joi.boolean(),
|
||||
helpCategory: Joi.valid(
|
||||
'JavaScript',
|
||||
'HTML-CSS',
|
||||
|
@ -2,6 +2,7 @@
|
||||
"name": "",
|
||||
"isUpcomingChange": true,
|
||||
"usesMultifileEditor": true,
|
||||
"hasEditableBoundaries": true,
|
||||
"dashedName": "",
|
||||
"order": 42,
|
||||
"time": "5 hours",
|
||||
|
Reference in New Issue
Block a user