-
-
Notifications
You must be signed in to change notification settings - Fork 129
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* Add basic setup * Add DrawingPage, Add editorHandler, Move localStorage setter outside of codemirror * Add tons of stuff * Add scrubber copy * Change types of stuff * Run code on mount too * Remove logging * Add potential ability to save code, add header * Add endpoint * Add titles * Add test * Save code on debounce * Update header * Remove log * Save title * Break out setup hook * Add canvas coordinates tooltip (#7270) * Add canvas coordinates tooltip * Don't recalculate tooltip size on every mouse move * Add edit mode to header (#7271) * Add edit mode to header * Remove practically useless code * Change back button label on save * Move back buttons * More label work * Update app/css/bootcamp/components/site-header.css --------- Co-authored-by: Jeremy Walker <[email protected]>
- Loading branch information
Showing
23 changed files
with
1,173 additions
and
121 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
class API::Bootcamp::DrawingsController < API::Bootcamp::BaseController | ||
before_action :use_drawing | ||
|
||
def update | ||
@drawing.update(code: params[:code]) if params[:code].present? | ||
@drawing.update(title: params[:title]) if params[:title].present? | ||
|
||
render json: {}, status: :ok | ||
end | ||
|
||
private | ||
def use_drawing | ||
@drawing = current_user.bootcamp_drawings.find_by!(uuid: params[:uuid]) | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
114 changes: 114 additions & 0 deletions
114
app/javascript/components/bootcamp/DrawingPage/DrawingPage.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,114 @@ | ||
import React, { useMemo, useState } from 'react' | ||
import { Header, StudentCodeGetter } from './Header/Header' | ||
import { | ||
Resizer, | ||
useResizablePanels, | ||
} from '../SolveExercisePage/hooks/useResize' | ||
import { CodeMirror } from '../SolveExercisePage/CodeMirror/CodeMirror' | ||
import ErrorBoundary from '../common/ErrorBoundary/ErrorBoundary' | ||
import { useDrawingEditorHandler } from './useDrawingEditorHandler' | ||
import { useLocalStorage } from '@uidotdev/usehooks' | ||
import Scrubber from './Scrubber/Scrubber' | ||
import { debounce } from 'lodash' | ||
import { useSetupDrawingPage } from './useSetupDrawingPage' | ||
|
||
export default function DrawingPage({ | ||
drawing, | ||
code, | ||
links, | ||
}: DrawingPageProps) { | ||
const [savingStateLabel, setSavingStateLabel] = useState<string>('') | ||
|
||
const { | ||
primarySize: LHSWidth, | ||
secondarySize: RHSWidth, | ||
handleMouseDown, | ||
} = useResizablePanels({ | ||
initialSize: 800, | ||
direction: 'horizontal', | ||
localStorageId: 'drawing-page-lhs', | ||
}) | ||
|
||
const { | ||
handleRunCode, | ||
handleEditorDidMount, | ||
getStudentCode, | ||
editorViewRef, | ||
viewContainerRef, | ||
animationTimeline, | ||
frames, | ||
} = useDrawingEditorHandler({ code, links, drawing }) | ||
|
||
const [editorLocalStorageValue, setEditorLocalStorageValue] = useLocalStorage( | ||
'bootcamp-editor-value-' + drawing.uuid, | ||
{ code: code.code, storedAt: code.storedAt } | ||
) | ||
|
||
useSetupDrawingPage({ | ||
code, | ||
editorLocalStorageValue, | ||
setEditorLocalStorageValue, | ||
}) | ||
|
||
const patchCodeOnDebounce = useMemo(() => { | ||
return debounce(() => { | ||
setSavingStateLabel('Saving...') | ||
patchDrawingCode(links, getStudentCode).then(() => | ||
setSavingStateLabel('Saved') | ||
) | ||
}, 5000) | ||
}, [setEditorLocalStorageValue]) | ||
|
||
return ( | ||
<div id="bootcamp-solve-exercise-page"> | ||
<Header | ||
links={links} | ||
savingStateLabel={savingStateLabel} | ||
drawing={drawing} | ||
/> | ||
<div className="page-body"> | ||
<div style={{ width: LHSWidth }} className="page-body-lhs"> | ||
<ErrorBoundary> | ||
<CodeMirror | ||
style={{ height: `100%` }} | ||
ref={editorViewRef} | ||
editorDidMount={handleEditorDidMount} | ||
handleRunCode={handleRunCode} | ||
setEditorLocalStorageValue={setEditorLocalStorageValue} | ||
onEditorChangeCallback={patchCodeOnDebounce} | ||
/> | ||
<Scrubber animationTimeline={animationTimeline} frames={frames} /> | ||
</ErrorBoundary> | ||
</div> | ||
<Resizer direction="vertical" handleMouseDown={handleMouseDown} /> | ||
{/* RHS */} | ||
<div className="page-body-rhs" style={{ width: RHSWidth }}> | ||
<div ref={viewContainerRef} id="view-container" /> | ||
</div> | ||
</div> | ||
</div> | ||
) | ||
} | ||
|
||
async function patchDrawingCode( | ||
links: DrawingPageProps['links'], | ||
getStudentCode: StudentCodeGetter | ||
) { | ||
const studentCode = getStudentCode() | ||
|
||
const response = await fetch(links.updateCode, { | ||
method: 'PATCH', | ||
headers: { | ||
'Content-Type': 'application/json', | ||
}, | ||
body: JSON.stringify({ | ||
code: studentCode, | ||
}), | ||
}) | ||
|
||
if (!response.ok) { | ||
throw new Error('Failed to save code') | ||
} | ||
|
||
return response.json() | ||
} |
109 changes: 109 additions & 0 deletions
109
app/javascript/components/bootcamp/DrawingPage/Header/Header.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,109 @@ | ||
import React, { useCallback, useState } from 'react' | ||
import { wrapWithErrorBoundary } from '@/components/bootcamp/common/ErrorBoundary/wrapWithErrorBoundary' | ||
import { assembleClassNames } from '@/utils/assemble-classnames' | ||
|
||
import { GraphicalIcon } from '@/components/common/GraphicalIcon' | ||
|
||
export type StudentCodeGetter = () => string | undefined | ||
|
||
const DEFAULT_SAVE_BUTTON_LABEL = 'Save' | ||
function _Header({ | ||
links, | ||
savingStateLabel, | ||
drawing, | ||
}: { savingStateLabel: string } & Pick<DrawingPageProps, 'links' | 'drawing'>) { | ||
const [titleInputValue, setTitleInputValue] = useState(drawing.title) | ||
const [editMode, setEditMode] = useState(false) | ||
const [titleSavingStateLabel, setTitleSavingStateLabel] = useState<string>( | ||
DEFAULT_SAVE_BUTTON_LABEL | ||
) | ||
|
||
const handleSaveTitle = useCallback(() => { | ||
setTitleSavingStateLabel('Saving...') | ||
patchDrawingTitle(links, titleInputValue) | ||
.then(() => { | ||
setTitleSavingStateLabel(DEFAULT_SAVE_BUTTON_LABEL) | ||
setEditMode(false) | ||
}) | ||
.catch(() => setTitleSavingStateLabel('Try again')) | ||
}, [links, titleInputValue]) | ||
|
||
return ( | ||
<div className="page-header"> | ||
<div className="ident"> | ||
<GraphicalIcon icon="logo" category="bootcamp" /> | ||
<div> | ||
<strong className="font-semibold">Exercism</strong> Bootcamp | ||
</div> | ||
</div> | ||
<div className="ml-auto flex items-center gap-12"> | ||
{savingStateLabel && ( | ||
<span className="text-xs text-gray-500 font-semibold mr-4"> | ||
{savingStateLabel} | ||
</span> | ||
)} | ||
<div className="flex items-center gap-12"> | ||
{editMode ? ( | ||
<> | ||
<button onClick={handleSaveTitle} className="btn-primary btn-xxs"> | ||
{titleSavingStateLabel} | ||
</button> | ||
<button | ||
className="btn-secondary btn-xxs" | ||
onClick={() => setEditMode(false)} | ||
> | ||
Cancel | ||
</button> | ||
<input | ||
value={titleInputValue} | ||
onChange={(e) => { | ||
setTitleInputValue(e.target.value) | ||
setTitleSavingStateLabel(DEFAULT_SAVE_BUTTON_LABEL) | ||
}} | ||
type="text" | ||
style={{ all: 'unset', borderBottom: '1px solid' }} | ||
/> | ||
</> | ||
) : ( | ||
<> | ||
<button onClick={() => setEditMode(true)}> | ||
<GraphicalIcon icon="edit" height={15} width={15} /> | ||
</button> | ||
<span>{titleInputValue}</span> | ||
</> | ||
)} | ||
</div> | ||
|
||
<a | ||
href={links.drawingsIndex} | ||
className={assembleClassNames('btn-secondary btn-xxs')} | ||
> | ||
Back to drawings | ||
</a> | ||
</div> | ||
</div> | ||
) | ||
} | ||
|
||
export const Header = wrapWithErrorBoundary(_Header) | ||
|
||
async function patchDrawingTitle( | ||
links: DrawingPageProps['links'], | ||
title: string | ||
) { | ||
const response = await fetch(links.updateCode, { | ||
method: 'PATCH', | ||
headers: { | ||
'Content-Type': 'application/json', | ||
}, | ||
body: JSON.stringify({ | ||
title, | ||
}), | ||
}) | ||
|
||
if (!response.ok) { | ||
throw new Error('Failed to save code') | ||
} | ||
|
||
return response.json() | ||
} |
45 changes: 45 additions & 0 deletions
45
app/javascript/components/bootcamp/DrawingPage/Scrubber/InformationWidgetToggleButton.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
import React from 'react' | ||
import { useCallback } from 'react' | ||
import useEditorStore from '../../SolveExercisePage/store/editorStore' | ||
import useTestStore from '../../SolveExercisePage/store/testStore' | ||
|
||
export function InformationWidgetToggleButton({ | ||
disabled, | ||
}: { | ||
disabled: boolean | ||
}) { | ||
const { | ||
toggleShouldShowInformationWidget, | ||
shouldShowInformationWidget, | ||
setHighlightedLine, | ||
} = useEditorStore() | ||
const { inspectedTestResult } = useTestStore() | ||
const handleToggleShouldShowInformationWidget = useCallback(() => { | ||
toggleShouldShowInformationWidget() | ||
|
||
if (!inspectedTestResult) return | ||
|
||
// if there is only one frame.. | ||
if (inspectedTestResult.frames.length === 1) { | ||
// ...and we are about to show information widget | ||
if (!shouldShowInformationWidget) { | ||
// highlight relevant line | ||
setHighlightedLine(inspectedTestResult.frames[0].line) | ||
} else { | ||
// if toggling's next step is off, remove highlight | ||
setHighlightedLine(0) | ||
} | ||
} | ||
}, [shouldShowInformationWidget, inspectedTestResult]) | ||
return ( | ||
<label className="switch"> | ||
<input | ||
disabled={disabled} | ||
type="checkbox" | ||
onChange={handleToggleShouldShowInformationWidget} | ||
checked={shouldShowInformationWidget} | ||
/> | ||
<span className="slider round"></span> | ||
</label> | ||
) | ||
} |
Oops, something went wrong.