feat: create action row and style Multi-file editor (#41579)
* feat: add action row & style editor * fix: separate conditional for splittter and reflex element * fix: move jaws whenever a line is deleted or added * feat: keep line indicator inside editable region * feat: add submit button and restyle decorator Co-authored-by: Oliver Eyton-Williams <ojeytonwilliams@gmail.com>
This commit is contained in:
@ -1,13 +1,58 @@
|
||||
import React from 'react';
|
||||
|
||||
import PropTypes from 'prop-types';
|
||||
import EditorTabs from './EditorTabs';
|
||||
|
||||
const ActionRow = () => (
|
||||
<div className='action-row'>
|
||||
<EditorTabs />
|
||||
</div>
|
||||
);
|
||||
const propTypes = {
|
||||
block: PropTypes.string,
|
||||
showConsole: PropTypes.bool,
|
||||
showNotes: PropTypes.bool,
|
||||
showPreview: PropTypes.bool,
|
||||
superBlock: PropTypes.string,
|
||||
switchDisplayTab: PropTypes.func
|
||||
};
|
||||
|
||||
const ActionRow = ({ switchDisplayTab, showPreview, showConsole }) => {
|
||||
const restartStep = () => {
|
||||
console.log('restart');
|
||||
};
|
||||
return (
|
||||
<div className='action-row'>
|
||||
<div>
|
||||
<h5 className='breadcrumbs-demo'>
|
||||
Responsive Web Design > Basic HTML Cat Photo App >{' '}
|
||||
<span>Step 23 of 213</span>
|
||||
</h5>
|
||||
</div>
|
||||
<div className='tabs-row'>
|
||||
<EditorTabs />
|
||||
<button
|
||||
className='restart-step-tab'
|
||||
onClick={() => restartStep()}
|
||||
role='tab'
|
||||
>
|
||||
Restart Step
|
||||
</button>
|
||||
<div className='panel-display-tabs'>
|
||||
<button
|
||||
className={showConsole ? 'active-tab' : ''}
|
||||
onClick={() => switchDisplayTab('showConsole')}
|
||||
role='tab'
|
||||
>
|
||||
JS Console
|
||||
</button>
|
||||
<button
|
||||
className={showPreview ? 'active-tab' : ''}
|
||||
onClick={() => switchDisplayTab('showPreview')}
|
||||
role='tab'
|
||||
>
|
||||
Show Preview
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
ActionRow.propTypes = propTypes;
|
||||
ActionRow.displayName = 'ActionRow';
|
||||
|
||||
export default ActionRow;
|
||||
|
@ -29,6 +29,20 @@ const reflexProps = {
|
||||
};
|
||||
|
||||
class DesktopLayout extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = { showNotes: false, showPreview: true, showConsole: false };
|
||||
this.switchDisplayTab = this.switchDisplayTab.bind(this);
|
||||
}
|
||||
|
||||
switchDisplayTab(displayTab) {
|
||||
this.setState(state => {
|
||||
return {
|
||||
[displayTab]: !state[displayTab]
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
getChallengeFile() {
|
||||
const { challengeFiles } = this.props;
|
||||
return first(Object.keys(challengeFiles).map(key => challengeFiles[key]));
|
||||
@ -45,16 +59,30 @@ class DesktopLayout extends Component {
|
||||
hasEditableBoundries
|
||||
} = this.props;
|
||||
|
||||
const { showPreview, showConsole } = this.state;
|
||||
|
||||
const challengeFile = this.getChallengeFile();
|
||||
const projectBasedChallenge = showUpcomingChanges && hasEditableBoundries;
|
||||
const isPreviewDisplayable = projectBasedChallenge
|
||||
? showPreview && hasPreview
|
||||
: hasPreview;
|
||||
const isConsoleDisplayable = projectBasedChallenge ? showConsole : true;
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
{showUpcomingChanges && hasEditableBoundries && <ActionRow />}
|
||||
{projectBasedChallenge && (
|
||||
<ActionRow switchDisplayTab={this.switchDisplayTab} {...this.state} />
|
||||
)}
|
||||
<ReflexContainer className='desktop-layout' orientation='vertical'>
|
||||
<ReflexElement flex={1} {...resizeProps}>
|
||||
{instructions}
|
||||
</ReflexElement>
|
||||
<ReflexSplitter propagate={true} {...resizeProps} />
|
||||
{!projectBasedChallenge && (
|
||||
<ReflexElement flex={1} {...resizeProps}>
|
||||
{instructions}
|
||||
</ReflexElement>
|
||||
)}
|
||||
{!projectBasedChallenge && (
|
||||
<ReflexSplitter propagate={true} {...resizeProps} />
|
||||
)}
|
||||
|
||||
<ReflexElement flex={1} {...resizeProps}>
|
||||
{challengeFile && (
|
||||
<ReflexContainer key={challengeFile.key} orientation='horizontal'>
|
||||
@ -68,15 +96,21 @@ class DesktopLayout extends Component {
|
||||
</Fragment>
|
||||
}
|
||||
</ReflexElement>
|
||||
<ReflexSplitter propagate={true} {...resizeProps} />
|
||||
<ReflexElement flex={0.25} {...reflexProps} {...resizeProps}>
|
||||
{testOutput}
|
||||
</ReflexElement>
|
||||
{isConsoleDisplayable && (
|
||||
<ReflexSplitter propagate={true} {...resizeProps} />
|
||||
)}
|
||||
{isConsoleDisplayable && (
|
||||
<ReflexElement flex={0.25} {...reflexProps} {...resizeProps}>
|
||||
{testOutput}
|
||||
</ReflexElement>
|
||||
)}
|
||||
</ReflexContainer>
|
||||
)}
|
||||
</ReflexElement>
|
||||
{hasPreview && <ReflexSplitter propagate={true} {...resizeProps} />}
|
||||
{hasPreview && (
|
||||
{isPreviewDisplayable && (
|
||||
<ReflexSplitter propagate={true} {...resizeProps} />
|
||||
)}
|
||||
{isPreviewDisplayable && (
|
||||
<ReflexElement flex={0.7} {...resizeProps}>
|
||||
{preview}
|
||||
</ReflexElement>
|
||||
|
@ -12,7 +12,9 @@ import {
|
||||
saveEditorContent,
|
||||
setEditorFocusability,
|
||||
setAccessibilityMode,
|
||||
updateFile
|
||||
updateFile,
|
||||
challengeTestsSelector,
|
||||
submitChallenge
|
||||
} from '../redux';
|
||||
import { userSelector, isDonationModalOpenSelector } from '../../../redux';
|
||||
import { Loader } from '../../../components/helpers';
|
||||
@ -43,6 +45,8 @@ const propTypes = {
|
||||
saveEditorContent: PropTypes.func.isRequired,
|
||||
setAccessibilityMode: PropTypes.func.isRequired,
|
||||
setEditorFocusability: PropTypes.func,
|
||||
submitChallenge: PropTypes.func,
|
||||
tests: PropTypes.arrayOf(PropTypes.object),
|
||||
theme: PropTypes.string,
|
||||
updateFile: PropTypes.func.isRequired
|
||||
};
|
||||
@ -53,11 +57,20 @@ const mapStateToProps = createSelector(
|
||||
inAccessibilityModeSelector,
|
||||
isDonationModalOpenSelector,
|
||||
userSelector,
|
||||
(canFocus, output, accessibilityMode, open, { theme = 'default' }) => ({
|
||||
challengeTestsSelector,
|
||||
(
|
||||
canFocus,
|
||||
output,
|
||||
accessibilityMode,
|
||||
open,
|
||||
{ theme = 'default' },
|
||||
tests
|
||||
) => ({
|
||||
canFocus: open ? false : canFocus,
|
||||
output,
|
||||
inAccessibilityMode: accessibilityMode,
|
||||
theme
|
||||
theme,
|
||||
tests
|
||||
})
|
||||
);
|
||||
|
||||
@ -66,7 +79,8 @@ const mapDispatchToProps = {
|
||||
saveEditorContent,
|
||||
setAccessibilityMode,
|
||||
setEditorFocusability,
|
||||
updateFile
|
||||
updateFile,
|
||||
submitChallenge
|
||||
};
|
||||
|
||||
const modeMap = {
|
||||
@ -140,6 +154,7 @@ class Editor extends Component {
|
||||
viewZoneId: null,
|
||||
startEditDecId: null,
|
||||
endEditDecId: null,
|
||||
insideEditDecId: null,
|
||||
viewZoneHeight: null
|
||||
};
|
||||
|
||||
@ -182,6 +197,13 @@ class Editor extends Component {
|
||||
this.focusOnEditor = this.focusOnEditor.bind(this);
|
||||
}
|
||||
|
||||
getEditableRegion = () => {
|
||||
const { challengeFiles, fileKey } = this.props;
|
||||
return challengeFiles[fileKey].editableRegionBoundaries
|
||||
? [...challengeFiles[fileKey].editableRegionBoundaries]
|
||||
: [];
|
||||
};
|
||||
|
||||
editorWillMount = monaco => {
|
||||
this._monaco = monaco;
|
||||
const { challengeFiles, fileKey } = this.props;
|
||||
@ -201,9 +223,7 @@ class Editor extends Component {
|
||||
);
|
||||
this.data.model = model;
|
||||
|
||||
const editableRegion = challengeFiles[fileKey].editableRegionBoundaries
|
||||
? [...challengeFiles[fileKey].editableRegionBoundaries]
|
||||
: [];
|
||||
const editableRegion = this.getEditableRegion();
|
||||
|
||||
if (editableRegion.length === 2)
|
||||
this.decorateForbiddenRanges(editableRegion);
|
||||
@ -224,7 +244,6 @@ class Editor extends Component {
|
||||
|
||||
editorDidMount = (editor, monaco) => {
|
||||
this._editor = editor;
|
||||
const { challengeFiles, fileKey } = this.props;
|
||||
editor.updateOptions({
|
||||
accessibilitySupport: this.props.inAccessibilityMode ? 'on' : 'auto'
|
||||
});
|
||||
@ -286,9 +305,7 @@ class Editor extends Component {
|
||||
}
|
||||
});
|
||||
|
||||
const editableBoundaries = challengeFiles[fileKey].editableRegionBoundaries
|
||||
? [...challengeFiles[fileKey].editableRegionBoundaries]
|
||||
: [];
|
||||
const editableBoundaries = this.getEditableRegion();
|
||||
|
||||
if (editableBoundaries.length === 2) {
|
||||
// TODO: is there a nicer approach/way of organising everything that
|
||||
@ -363,7 +380,7 @@ class Editor extends Component {
|
||||
this.data.viewZoneHeight = domNode.offsetHeight;
|
||||
|
||||
var background = document.createElement('div');
|
||||
background.style.background = 'lightgreen';
|
||||
// background.style.background = 'lightgreen';
|
||||
|
||||
// We have to wait for the viewZone to finish rendering before adjusting the
|
||||
// position of the overlayWidget (i.e. trigger it via onComputedHeight). If
|
||||
@ -392,7 +409,7 @@ class Editor extends Component {
|
||||
this.data.outputZoneHeight = outputNode.offsetHeight;
|
||||
|
||||
var background = document.createElement('div');
|
||||
background.style.background = 'lightpink';
|
||||
// background.style.background = 'lightpink';
|
||||
|
||||
// We have to wait for the viewZone to finish rendering before adjusting the
|
||||
// position of the overlayWidget (i.e. trigger it via onComputedHeight). If
|
||||
@ -413,19 +430,14 @@ class Editor extends Component {
|
||||
const { description } = this.props;
|
||||
var domNode = document.createElement('div');
|
||||
var desc = document.createElement('div');
|
||||
var button = document.createElement('button');
|
||||
button.innerHTML = 'Run the Tests (Ctrl + Enter)';
|
||||
button.onclick = () => {
|
||||
const { executeChallenge } = this.props;
|
||||
executeChallenge();
|
||||
};
|
||||
|
||||
domNode.appendChild(desc);
|
||||
domNode.appendChild(button);
|
||||
var descContainer = document.createElement('div');
|
||||
descContainer.classList.add('description-container');
|
||||
domNode.classList.add('editor-upper-jaw');
|
||||
domNode.appendChild(descContainer);
|
||||
descContainer.appendChild(desc);
|
||||
desc.innerHTML = description;
|
||||
|
||||
desc.style.background = 'white';
|
||||
domNode.style.background = 'lightgreen';
|
||||
// desc.style.background = 'white';
|
||||
// domNode.style.background = 'lightgreen';
|
||||
// TODO: the solution is probably just to use an overlay that's forced to
|
||||
// follow the decorations.
|
||||
// TODO: this is enough for Firefox, but Chrome needs more before the
|
||||
@ -436,7 +448,7 @@ class Editor extends Component {
|
||||
|
||||
domNode.setAttribute('aria-hidden', true);
|
||||
|
||||
domNode.style.background = 'lightYellow';
|
||||
// domNode.style.background = 'lightYellow';
|
||||
domNode.style.left = this._editor.getLayoutInfo().contentLeft + 'px';
|
||||
domNode.style.width = this._editor.getLayoutInfo().contentWidth + 'px';
|
||||
domNode.style.top = this.getViewZoneTop();
|
||||
@ -449,11 +461,23 @@ class Editor extends Component {
|
||||
const outputNode = document.createElement('div');
|
||||
const statusNode = document.createElement('div');
|
||||
const hintNode = document.createElement('div');
|
||||
outputNode.appendChild(statusNode);
|
||||
outputNode.appendChild(hintNode);
|
||||
const editorActionRow = document.createElement('div');
|
||||
editorActionRow.classList.add('action-row-container');
|
||||
outputNode.classList.add('editor-lower-jaw');
|
||||
outputNode.appendChild(editorActionRow);
|
||||
hintNode.setAttribute('id', 'test-output');
|
||||
statusNode.setAttribute('id', 'test-status');
|
||||
statusNode.innerHTML = '// tests';
|
||||
var button = document.createElement('button');
|
||||
button.setAttribute('id', 'test-button');
|
||||
button.classList.add('btn-block');
|
||||
button.innerHTML = 'Check Your Code (Ctrl + Enter)';
|
||||
editorActionRow.appendChild(button);
|
||||
editorActionRow.appendChild(statusNode);
|
||||
editorActionRow.appendChild(hintNode);
|
||||
button.onclick = () => {
|
||||
const { executeChallenge } = this.props;
|
||||
executeChallenge();
|
||||
};
|
||||
|
||||
// TODO: does it?
|
||||
// The z-index needs increasing as ViewZones default to below the lines.
|
||||
@ -528,6 +552,19 @@ class Editor extends Component {
|
||||
return target.deltaDecorations(oldIds, [lineDecoration]);
|
||||
}
|
||||
|
||||
highlightEditableLines(stickiness, target, range, oldIds = []) {
|
||||
const lineDecoration = {
|
||||
range,
|
||||
options: {
|
||||
isWholeLine: true,
|
||||
linesDecorationsClassName: 'myEditableLineDecoration',
|
||||
className: 'do-not-edit',
|
||||
stickiness
|
||||
}
|
||||
};
|
||||
return target.deltaDecorations(oldIds, [lineDecoration]);
|
||||
}
|
||||
|
||||
highlightText(stickiness, target, range, oldIds = []) {
|
||||
const inlineDecoration = {
|
||||
range,
|
||||
@ -655,6 +692,17 @@ class Editor extends Component {
|
||||
return this.positionsToRange(model, positions);
|
||||
});
|
||||
|
||||
const editableRange = this.positionsToRange(model, [
|
||||
editableRegion[0] + 1,
|
||||
editableRegion[1] - 1
|
||||
]);
|
||||
|
||||
this.data.insideEditDecId = this.highlightEditableLines(
|
||||
this._monaco.editor.TrackedRangeStickiness.AlwaysGrowsWhenTypingAtEdges,
|
||||
model,
|
||||
editableRange
|
||||
);
|
||||
|
||||
// if the forbidden range includes the top of the editor
|
||||
// we simply don't add those decorations
|
||||
if (forbiddenRanges[0][1] > 0) {
|
||||
@ -743,53 +791,25 @@ class Editor extends Component {
|
||||
});
|
||||
};
|
||||
|
||||
// Make sure the zone tracks the decoration (i.e. the region)
|
||||
const handleHintsZoneChange = id => {
|
||||
const startOfZone = toStartOfLine(
|
||||
model.getDecorationRange(id)
|
||||
).collapseToStart();
|
||||
// the decoration needs adjusting if the user creates a line immediately
|
||||
// before the greyed out region...
|
||||
const lineOneRange = this.translateRange(startOfZone, -2);
|
||||
// or immediately after it
|
||||
const lineTwoRange = this.translateRange(startOfZone, -1);
|
||||
|
||||
for (const lineRange of newLineRanges) {
|
||||
const shouldMoveZone = this._monaco.Range.areIntersectingOrTouching(
|
||||
lineRange,
|
||||
lineOneRange.plusRange(lineTwoRange)
|
||||
);
|
||||
|
||||
if (shouldMoveZone) {
|
||||
this.updateOutputZone();
|
||||
}
|
||||
// Make sure the zone tracks the decoration (i.e. the region), which might
|
||||
// have changed if a line has been added or removed
|
||||
const handleHintsZoneChange = () => {
|
||||
if (newLineRanges.length > 0 || deletedLine > 0) {
|
||||
this.updateOutputZone();
|
||||
}
|
||||
};
|
||||
|
||||
// Make sure the zone tracks the decoration (i.e. the region)
|
||||
const handleDescriptionZoneChange = id => {
|
||||
const endOfZone = toLastLine(
|
||||
model.getDecorationRange(id)
|
||||
).collapseToStart();
|
||||
// the decoration needs adjusting if the user creates a line immediately
|
||||
// before the editable region.
|
||||
const lineOneRange = this.translateRange(endOfZone, -1);
|
||||
|
||||
for (const lineRange of newLineRanges) {
|
||||
const shouldMoveZone = this._monaco.Range.areIntersectingOrTouching(
|
||||
lineRange,
|
||||
lineOneRange
|
||||
);
|
||||
|
||||
if (shouldMoveZone) {
|
||||
this.updateViewZone();
|
||||
}
|
||||
// Make sure the zone tracks the decoration (i.e. the region), which might
|
||||
// have changed if a line has been added or removed
|
||||
const handleDescriptionZoneChange = () => {
|
||||
if (newLineRanges.length > 0 || deletedLine > 0) {
|
||||
this.updateViewZone();
|
||||
}
|
||||
};
|
||||
|
||||
// Stops the greyed out region from covering the editable region. Does not
|
||||
// change the font decoration.
|
||||
const preventOverlap = id => {
|
||||
const preventOverlap = (id, stickiness, highlightFunction) => {
|
||||
// Even though the decoration covers the whole line, it has a
|
||||
// startColumn that moves. toStartOfLine ensures that the
|
||||
// comparison detects if any change has occurred on that line
|
||||
@ -821,17 +841,15 @@ class Editor extends Component {
|
||||
|
||||
if (touchingDeleted) {
|
||||
// TODO: if they undo this should be reversed
|
||||
const decorations = this.highlightLines(
|
||||
this._monaco.editor.TrackedRangeStickiness
|
||||
.NeverGrowsWhenTypingAtEdges,
|
||||
const decorations = highlightFunction(
|
||||
stickiness,
|
||||
model,
|
||||
newCoveringRange,
|
||||
[id]
|
||||
id
|
||||
);
|
||||
|
||||
this.updateOutputZone();
|
||||
// when there's a change, decorations will be [oldId, newId]
|
||||
return decorations.slice(-1)[0];
|
||||
return decorations;
|
||||
} else {
|
||||
return id;
|
||||
}
|
||||
@ -839,7 +857,17 @@ class Editor extends Component {
|
||||
|
||||
// we only need to handle the special case of the second region being
|
||||
// pulled up, the first region already behaves correctly.
|
||||
this.data.endEditDecId = preventOverlap(this.data.endEditDecId);
|
||||
this.data.endEditDecId = preventOverlap(
|
||||
this.data.endEditDecId,
|
||||
this._monaco.editor.TrackedRangeStickiness.GrowsOnlyWhenTypingBefore,
|
||||
this.highlightLines
|
||||
);
|
||||
|
||||
this.data.insideEditDecId = preventOverlap(
|
||||
this.data.insideEditDecId,
|
||||
this._monaco.editor.TrackedRangeStickiness.AlwaysGrowsWhenTypingAtEdges,
|
||||
this.highlightEditableLines
|
||||
);
|
||||
|
||||
// TODO: do the same for the description widget
|
||||
// this has to be handle differently, because we care about the END
|
||||
@ -847,10 +875,10 @@ class Editor extends Component {
|
||||
// if the editable region includes the first line, the first decoration
|
||||
// will be missing.
|
||||
if (this.data.startEditDecId) {
|
||||
handleDescriptionZoneChange(this.data.startEditDecId);
|
||||
handleDescriptionZoneChange();
|
||||
warnUser(this.data.startEditDecId);
|
||||
}
|
||||
handleHintsZoneChange(this.data.endEditDecId);
|
||||
handleHintsZoneChange();
|
||||
warnUser(this.data.endEditDecId);
|
||||
});
|
||||
}
|
||||
@ -889,7 +917,47 @@ class Editor extends Component {
|
||||
}
|
||||
|
||||
if (this._editor) {
|
||||
const { output } = this.props;
|
||||
const { output, tests } = this.props;
|
||||
const editableRegion = this.getEditableRegion();
|
||||
if (this.props.tests !== prevProps.tests && editableRegion.length === 2) {
|
||||
const challengeComplete = tests.every(test => test.pass && !test.err);
|
||||
const chellengeHasErrors = tests.some(test => test.err);
|
||||
|
||||
if (challengeComplete) {
|
||||
let testButton = document.getElementById('test-button');
|
||||
testButton.innerHTML =
|
||||
'Submit your code and go to next challenge (Ctrl + Enter)';
|
||||
testButton.onclick = () => {
|
||||
const { submitChallenge } = this.props;
|
||||
submitChallenge();
|
||||
};
|
||||
|
||||
let editableRegionDecorators = document.getElementsByClassName(
|
||||
'myEditableLineDecoration'
|
||||
);
|
||||
if (editableRegionDecorators.length > 0) {
|
||||
for (var i of editableRegionDecorators) {
|
||||
i.classList.add('tests-passed');
|
||||
}
|
||||
}
|
||||
document.getElementById('test-output').innerHTML = '';
|
||||
document.getElementById('test-status').innerHTML =
|
||||
'✅ Step completed.';
|
||||
} else if (chellengeHasErrors) {
|
||||
const wordsArray = [
|
||||
"Not quite. Here's a hint:",
|
||||
'Try again. This might help:',
|
||||
'Keep trying. A quick hint for you:',
|
||||
"You're getting there. This may help:",
|
||||
"Hang in there. You'll get there. A hint:",
|
||||
"Don't give up. Here's a hint to get you thinking:"
|
||||
];
|
||||
document.getElementById('test-status').innerHTML = `✖️ ${
|
||||
wordsArray[Math.floor(Math.random() * wordsArray.length)]
|
||||
}`;
|
||||
document.getElementById('test-output').innerHTML = `${output[1]}`;
|
||||
}
|
||||
}
|
||||
if (this.props.output !== prevProps.output && this._outputNode) {
|
||||
// TODO: output gets wiped when the preview gets updated, keeping the
|
||||
// display is an anti-pattern (the render should not ignore props!).
|
||||
@ -897,14 +965,6 @@ class Editor extends Component {
|
||||
// (shownHint,maybe) and have that persist through previews. But, for
|
||||
// now:
|
||||
if (output) {
|
||||
if (output[0]) {
|
||||
document.getElementById('test-status').innerHTML = output[0];
|
||||
}
|
||||
|
||||
if (output[1]) {
|
||||
document.getElementById('test-output').innerHTML = output[1];
|
||||
}
|
||||
|
||||
// if either id exists, the editable region exists
|
||||
// TODO: add a layer of abstraction: we should be interacting with
|
||||
// the editable region, not the ids
|
||||
|
@ -6,10 +6,6 @@
|
||||
color: var(--primary-color);
|
||||
}
|
||||
|
||||
[widgetid='my.overlay.widget'] {
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.vs .monaco-scrollable-element > .scrollbar > .slider {
|
||||
z-index: 11;
|
||||
}
|
||||
@ -17,3 +13,64 @@
|
||||
.editor-container {
|
||||
background: var(--editor-background);
|
||||
}
|
||||
|
||||
.breadcrumbs-demo {
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.breadcrumbs-demo span {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.tabs-row {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.action-row button {
|
||||
padding: 4px 16px;
|
||||
}
|
||||
|
||||
.active-tab {
|
||||
border-color: var(--secondary-color);
|
||||
background-color: var(--secondary-color);
|
||||
color: var(--secondary-background);
|
||||
}
|
||||
|
||||
/* [widgetid='my.overlay.widget'] {
|
||||
padding: 10px;
|
||||
} */
|
||||
|
||||
.editor-upper-jaw,
|
||||
.editor-lower-jaw {
|
||||
padding: 15px 15px 15px 0px;
|
||||
}
|
||||
|
||||
.action-row-container,
|
||||
.description-container {
|
||||
background-color: var(--secondary-background);
|
||||
padding: 10px;
|
||||
border: 2px solid var(--tertiary-background);
|
||||
max-width: 700px;
|
||||
}
|
||||
|
||||
#description p:last-child {
|
||||
margin: 0px;
|
||||
}
|
||||
|
||||
#test-status {
|
||||
padding-bottom: 5px;
|
||||
padding-top: 5px;
|
||||
}
|
||||
|
||||
.myEditableLineDecoration {
|
||||
background-color: var(--gray-45);
|
||||
width: 15px !important;
|
||||
margin-left: 5px !important;
|
||||
margin-right: 5px !important;
|
||||
}
|
||||
|
||||
.myEditableLineDecoration.tests-passed {
|
||||
background-color: #4caf50;
|
||||
}
|
||||
|
40
client/src/templates/Challenges/components/BreadCrumb.js
Normal file
40
client/src/templates/Challenges/components/BreadCrumb.js
Normal file
@ -0,0 +1,40 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Link } from '../../../components/helpers/index';
|
||||
|
||||
import './challenge-title.css';
|
||||
import i18next from 'i18next';
|
||||
|
||||
const propTypes = {
|
||||
block: PropTypes.string,
|
||||
superBlock: PropTypes.string
|
||||
};
|
||||
|
||||
function BreadCrumb({ block, superBlock }) {
|
||||
return (
|
||||
<div className='challenge-title-breadcrumbs'>
|
||||
<Link
|
||||
className='breadcrumb-left'
|
||||
state={{ breadcrumbBlockClick: block }}
|
||||
to={`/learn/${superBlock}`}
|
||||
>
|
||||
<span className='ellipsis'>
|
||||
{i18next.t(`intro:${superBlock}.title`)}
|
||||
</span>
|
||||
</Link>
|
||||
<div className='breadcrumb-center' />
|
||||
<Link
|
||||
className='breadcrumb-right'
|
||||
state={{ breadcrumbBlockClick: block }}
|
||||
to={`/learn/${superBlock}/#${block}`}
|
||||
>
|
||||
{i18next.t(`intro:${superBlock}.blocks.${block}.title`)}
|
||||
</Link>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
BreadCrumb.displayName = 'BreadCrumb';
|
||||
BreadCrumb.propTypes = propTypes;
|
||||
|
||||
export default BreadCrumb;
|
@ -1,10 +1,11 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Link } from '../../../components/helpers/index';
|
||||
import i18next from 'i18next';
|
||||
|
||||
import './challenge-title.css';
|
||||
import GreenPass from '../../../assets/icons/GreenPass';
|
||||
import i18next from 'i18next';
|
||||
import BreadCrumb from './BreadCrumb';
|
||||
|
||||
const propTypes = {
|
||||
block: PropTypes.string,
|
||||
@ -31,25 +32,7 @@ function ChallengeTitle({
|
||||
{i18next.t('misc.translation-pending')}
|
||||
</Link>
|
||||
)}
|
||||
<div className='challenge-title-breadcrumbs'>
|
||||
<Link
|
||||
className='breadcrumb-left'
|
||||
state={{ breadcrumbBlockClick: block }}
|
||||
to={`/learn/${superBlock}`}
|
||||
>
|
||||
<span className='ellipsis'>
|
||||
{i18next.t(`intro:${superBlock}.title`)}
|
||||
</span>
|
||||
</Link>
|
||||
<div className='breadcrumb-center' />
|
||||
<Link
|
||||
className='breadcrumb-right'
|
||||
state={{ breadcrumbBlockClick: block }}
|
||||
to={`/learn/${superBlock}/#${block}`}
|
||||
>
|
||||
{i18next.t(`intro:${superBlock}.blocks.${block}.title`)}
|
||||
</Link>
|
||||
</div>
|
||||
<BreadCrumb block={block} superBlock={superBlock} />
|
||||
<div className='challenge-title'>
|
||||
<div className='title-text'>
|
||||
<b>{children}</b>
|
||||
|
Reference in New Issue
Block a user