Skip to content

Commit

Permalink
Merge pull request #2408 from codecrafters-io/code-mirror/add-code-mi…
Browse files Browse the repository at this point in the history
…rror-to-file-diff-card

Switch Admin Course Updates page to `CodeMirror`
  • Loading branch information
VasylMarchuk authored Nov 20, 2024
2 parents 801c4b2 + a0acaaa commit d32a1bf
Show file tree
Hide file tree
Showing 15 changed files with 196 additions and 17 deletions.
35 changes: 31 additions & 4 deletions app/components/file-diff-card.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,35 @@
<span class="font-mono text-xs text-gray-600 dark:text-gray-300 bold">{{@filename}}</span>
</div>

<div class="text-sm leading-6">
{{! @glint-expect-error: not ts-ified yet }}
<SyntaxHighlightedDiff @code={{@code}} @language={{@language}} @shouldCollapseUnchangedLines={{true}} @forceDarkTheme={{@forceDarkTheme}} />
</div>
{{#if @useCodeMirror}}
{{#let (diff-to-document @code) as |document|}}
<CodeMirror
@document={{document.current}}
@originalDocument={{document.original}}
@filename={{@filename}}
@language={{@language}}
@allowMultipleSelections={{true}}
@bracketMatching={{true}}
@collapseUnchanged={{true}}
@crosshairCursor={{true}}
@drawSelection={{true}}
@rectangularSelection={{true}}
@readOnly={{true}}
@highlightSelectionMatches={{true}}
@highlightSpecialChars={{true}}
@theme={{this.codeMirrorTheme}}
class="block text-sm"
/>
{{/let}}
{{else}}
<div class="text-sm leading-6">
{{! @glint-expect-error: not ts-ified yet }}
<SyntaxHighlightedDiff
@code={{or @code ""}}
@language={{@language}}
@shouldCollapseUnchangedLines={{true}}
@forceDarkTheme={{@forceDarkTheme}}
/>
</div>
{{/if}}
</div>
32 changes: 29 additions & 3 deletions app/components/file-diff-card.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,43 @@
import { service } from '@ember/service';
import Component from '@glimmer/component';
import type DarkModeService from 'codecrafters-frontend/services/dark-mode';
import { codeCraftersDark, codeCraftersLight } from 'codecrafters-frontend/utils/code-mirror-themes';

interface Signature {
Element: HTMLDivElement;

Args: {
code: string;
language: string;
/**
* Code to render in CodeMirror/SyntaxHighlightedDiff
*/
code?: string;
/**
* Filename to render in the header.
* Also used to auto-detect language for code formatting
*/
filename: string;
/**
* Always render CodeMirror/SyntaxHighlightedDiff using Dark Theme
*/
forceDarkTheme?: boolean;
/**
* Override language auto-detected from `filename` and set it manually
*/
language: string;
/**
* Use CodeMirror instead of SyntaxHighlightedDiff for rendering the diff
*/
useCodeMirror?: boolean;
};
}

export default class FileDiffCardComponent extends Component<Signature> {}
export default class FileDiffCardComponent extends Component<Signature> {
@service declare darkMode: DarkModeService;

get codeMirrorTheme() {
return this.darkMode.isEnabled || this.args.forceDarkTheme ? codeCraftersDark : codeCraftersLight;
}
}

declare module '@glint/environment-ember-loose/registry' {
export default interface Registry {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
{{if @chunk.isCollapsedAtBottom 'mb-0' 'border-b'}}"
role="button"
{{on "click" this.handleClick}}
data-test-expandable-chunk
>
<span class="flex flex-col justify-center h-full pr-2">
{{#if @chunk.isCollapsedAtTop}}
Expand Down
31 changes: 31 additions & 0 deletions app/controllers/demo/file-diff-card.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import Controller from '@ember/controller';
import { action } from '@ember/object';
import { tracked } from '@glimmer/tracking';
import EXAMPLE_DOCUMENTS, { DiffBasedExampleDocument } from 'codecrafters-frontend/utils/code-mirror-documents';

const OPTION_DEFAULTS = {
forceDarkTheme: false,
selectedDocumentIndex: 1,
useCodeMirror: true,
};

export default class DemoFileDiffCardController extends Controller {
@tracked documents: DiffBasedExampleDocument[] = EXAMPLE_DOCUMENTS;

@tracked forceDarkTheme: boolean = OPTION_DEFAULTS.forceDarkTheme;
@tracked selectedDocumentIndex: number = OPTION_DEFAULTS.selectedDocumentIndex;
@tracked useCodeMirror: boolean = OPTION_DEFAULTS.useCodeMirror;

get selectedDocument() {
return this.documents[this.selectedDocumentIndex] || DiffBasedExampleDocument.createEmpty();
}

@action resetAllOptions() {
Object.assign(this, OPTION_DEFAULTS);
}

@action selectedDocumentIndexDidChange(event: Event) {
const target: HTMLSelectElement = event.target as HTMLSelectElement;
this.selectedDocumentIndex = target.selectedIndex;
}
}
1 change: 1 addition & 0 deletions app/router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -106,5 +106,6 @@ Router.map(function () {
this.route('code-mirror');
this.route('dark-mode-toggle');
this.route('file-contents-card');
this.route('file-diff-card');
});
});
17 changes: 17 additions & 0 deletions app/routes/demo/file-diff-card.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import Route from '@ember/routing/route';

const QUERY_PARAMS = ['forceDarkTheme', 'selectedDocumentIndex', 'useCodeMirror'];

interface QueryParamOptions {
[key: string]: {
replace: boolean;
};
}

export default class DemoFileDiffCardRoute extends Route {
queryParams = QUERY_PARAMS.reduce<QueryParamOptions>((acc, param) => {
acc[param] = { replace: true };

return acc;
}, {});
}
1 change: 1 addition & 0 deletions app/styles/app.css
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
@import 'pages/course-stage-solution-page.scss';
@import 'pages/code-walkthrough-page.scss';
@import 'pages/course-admin-submission-diff.scss';
@import 'pages/course-admin-updates-diff.scss';

/* purgecss start ignore */
@import '@typeform/embed/build/css/popup.css';
Expand Down
3 changes: 3 additions & 0 deletions app/styles/pages/course-admin-updates-diff.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.course-admin-updates-diff-container code-mirror .cm-collapsedLines {
margin-left: -24px;
}
3 changes: 2 additions & 1 deletion app/templates/course-admin/update.hbs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<div class="bg-white min-h-screen">
<div class="container mx-auto pt-4 pb-10 px-6">
<div class="container mx-auto pt-4 pb-10 px-6 course-admin-updates-diff-container">
<div class="pt-3 pb-6 border-b mb-6">
<TertiaryLinkButton @route="course-admin.updates" @models={{array @model.update.course.slug}} @size="small" class="pl-1.5 mb-3">
<div class="flex items-center">
Expand Down Expand Up @@ -78,6 +78,7 @@
@filename="course-definition.yml"
@code={{@model.update.definitionFileContentsDiff}}
@language="yaml"
@useCodeMirror={{true}}
data-test-file-contents-diff
/>
</div>
Expand Down
10 changes: 10 additions & 0 deletions app/templates/demo.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,16 @@
class={{if (eq this.router.currentRouteName "demo.file-contents-card") "font-medium text-teal-500" "text-gray-600 dark:text-gray-200"}}
>FileContentsCard</span>
</LinkTo>
<LinkTo
@route="demo.file-diff-card"
role="button"
class="flex flex-shrink-0 items-center px-3 py-2 mr-2 border-b-2 text-sm
{{if (eq this.router.currentRouteName 'demo.file-diff-card') 'border-teal-500' 'border-gray-100 dark:border-gray-700'}}"
>
<span
class={{if (eq this.router.currentRouteName "demo.file-diff-card") "font-medium text-teal-500" "text-gray-600 dark:text-gray-200"}}
>FileDiffCard</span>
</LinkTo>
<LinkTo
@route="demo.dark-mode-toggle"
role="button"
Expand Down
4 changes: 2 additions & 2 deletions app/templates/demo/code-mirror.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
"flex cursor-default select-none text-nowrap mx-2"
as |groupClasses labelClasses|
}}
<codemirror-options class="{{groupClasses}} -mt-8">
<codemirror-options class="{{groupClasses}} border-dotted -mt-8">
<codemirror-options-left class="flex flex-grow flex-wrap">
<label class="{{labelClasses}}" title="Show an outline around the component's element">
<Input @type="checkbox" @checked={{this.outline}} />
Expand Down Expand Up @@ -270,7 +270,7 @@
</label>
</codemirror-options>

<codemirror-options class="{{groupClasses}} border-dotted">
<codemirror-options class="{{groupClasses}}">
<codemirror-options-left class="flex flex-grow flex-wrap">
<label
class="{{labelClasses}}"
Expand Down
50 changes: 50 additions & 0 deletions app/templates/demo/file-diff-card.hbs
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
{{page-title "FileDiffCard"}}

{{#let
"flex flex-wrap text-sm font-semibold text-gray-600 dark:text-gray-200 border-b border-gray-200 dark:border-gray-700
py-2"
"flex cursor-default select-none text-nowrap mx-2"
as |groupClasses labelClasses|
}}
<component-options class="{{groupClasses}} -mt-8">
<component-options-left class="flex flex-grow flex-wrap">
<label class="{{labelClasses}}" title="File for the component to render (select a preset)">
<span class="mr-2">File:</span>
<select class="dark:bg-gray-700" {{on "change" this.selectedDocumentIndexDidChange}}>
{{#each this.documents as |doc index|}}
<option selected={{eq this.selectedDocumentIndex index}}>{{doc.filename}} ({{doc.language}})</option>
{{/each}}
</select>
</label>
</component-options-left>
<component-options-right class="flex flex-wrap">
<label class="{{labelClasses}}" title="Use CodeMirror instead of SyntaxHighlightedDiff for rendering the diff">
<Input @type="checkbox" @checked={{this.useCodeMirror}} />
<span class="ml-2">useCodeMirror</span>
</label>
<label class="{{labelClasses}}" title="Always render CodeMirror using Dark Theme">
<Input @type="checkbox" @checked={{this.forceDarkTheme}} />
<span class="ml-2">forceDarkTheme</span>
</label>
</component-options-right>
</component-options>
{{/let}}

<FileDiffCard
@useCodeMirror={{this.useCodeMirror}}
@code={{this.selectedDocument.diff}}
@filename={{this.selectedDocument.filename}}
@language={{this.selectedDocument.language}}
@forceDarkTheme={{this.forceDarkTheme}}
class="mt-4 {{if this.forceDarkTheme 'dark'}}"
/>

<div class="my-2 text-right">
<span
role="button"
class="text-xs text-blue-600 hover:text-blue-800 dark:text-blue-200 dark:hover:text-blue-400 underline cursor-pointer"
{{on "click" this.resetAllOptions}}
>Reset all options</span>
</div>

{{outlet}}
4 changes: 4 additions & 0 deletions app/utils/code-mirror-documents.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,10 @@ export class DiffBasedExampleDocument extends ExampleDocument {

this.diff = diff;
}

static createEmpty() {
return new this({ filename: 'empty.txt', language: 'text' });
}
}

export default [
Expand Down
15 changes: 12 additions & 3 deletions tests/acceptance/course-admin/view-update-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ module('Acceptance | course-admin | view-update', function (hooks) {

const update = this.server.create('course-definition-update', {
course: this.server.schema.courses.findBy({ slug: 'redis' }),
definitionFileContentsDiff: 'old contents',
definitionFileContentsDiff: ' old contents\n',
description: 'Updated stage instructions for stage 1 & stage 2',
lastErrorMessage: null,
lastSyncedAt: new Date(2020, 1, 1),
Expand All @@ -70,12 +70,21 @@ module('Acceptance | course-admin | view-update', function (hooks) {
await updatesPage.visit({ course_slug: 'redis' });
await updatesPage.updateListItems[0].clickOnViewUpdateButton();

update.update('definitionFileContentsDiff', '+ updated diff');
update.update('definitionFileContentsDiff', '-old contents\n+updated diff\n');

await updatePage.clickOnSyncWithGitHubButton();
await settled(); // Investigate why clickable() doesn't call settled()

assert.ok(updatePage.fileContentsDiff.text.includes('+ updated diff'), 'diff should be updated after syncing with github');
assert.strictEqual(
updatePage.fileContentsDiff.codeMirror.content.changedLines[0].text,
'updated diff',
'diff should be updated after syncing with github',
);
assert.strictEqual(
updatePage.fileContentsDiff.codeMirror.content.deletedChunks[0].text,
'old contents',
'diff should be updated after syncing with github',
);
});

test('it should properly be properly rendered as an html', async function (assert) {
Expand Down
6 changes: 3 additions & 3 deletions tests/pages/course-admin/update-page.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { attribute, clickable, collection, create, visitable } from 'ember-cli-page-object';
import { attribute, clickable, create, visitable } from 'ember-cli-page-object';
import codeMirror from 'codecrafters-frontend/tests/pages/components/code-mirror';

export default create({
applyUpdateButton: {
Expand All @@ -12,9 +13,8 @@ export default create({
},

fileContentsDiff: {
expandableChunks: collection('[data-test-expandable-chunk]'),

scope: '[data-test-file-contents-diff]',
codeMirror,
},

description: {
Expand Down

0 comments on commit d32a1bf

Please sign in to comment.