Skip to content

Commit

Permalink
feat(training): persistence of code modifications
Browse files Browse the repository at this point in the history
  • Loading branch information
matthieu-crouzet committed Dec 11, 2024
1 parent 1d63fad commit 07049b5
Show file tree
Hide file tree
Showing 4 changed files with 120 additions and 17 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
inject,
input,
OnDestroy,
OnInit,
untracked,
ViewChild,
ViewEncapsulation,
Expand All @@ -25,6 +26,9 @@ import {
FormsModule,
ReactiveFormsModule,
} from '@angular/forms';
import {
DfModalService,
} from '@design-factory/design-factory';
import {
LoggerService,
} from '@o3r/logger';
Expand All @@ -44,6 +48,7 @@ import {
} from 'ngx-monaco-tree';
import {
BehaviorSubject,
combineLatest,
combineLatestWith,
debounceTime,
distinctUntilChanged,
Expand All @@ -54,10 +59,12 @@ import {
Observable,
of,
share,
shareReplay,
skip,
startWith,
Subject,
switchMap,
take,
} from 'rxjs';
import {
checkIfPathInMonacoTree,
Expand All @@ -69,6 +76,9 @@ import {
import {
CodeEditorControlComponent,
} from '../code-editor-control';
import {
SaveCodeDialogComponent,
} from '../save-code-dialog';

declare global {
interface Window {
Expand Down Expand Up @@ -117,7 +127,7 @@ export interface TrainingProject {
templateUrl: './code-editor-view.component.html',
styleUrl: './code-editor-view.component.scss'
})
export class CodeEditorViewComponent implements OnDestroy {
export class CodeEditorViewComponent implements OnDestroy, OnInit {
/**
* @see {FormBuilder}
*/
Expand Down Expand Up @@ -163,7 +173,7 @@ export class CodeEditorViewComponent implements OnDestroy {
: of([])
),
filter((tree) => tree.length > 0),
share()
shareReplay(1)
);

/**
Expand Down Expand Up @@ -205,11 +215,17 @@ export class CodeEditorViewComponent implements OnDestroy {
}))
);

private readonly fileContentLoaded$ = this.form.controls.file.valueChanges.pipe(
private readonly modalService = inject(DfModalService);
private readonly forceReload = new Subject<void>();

private readonly fileContentLoaded$ = combineLatest([
this.form.controls.file.valueChanges,
this.forceReload.pipe(startWith(undefined))
]).pipe(
takeUntilDestroyed(),
combineLatestWith(this.cwdTree$),
filter(([path, monacoTree]) => !!path && checkIfPathInMonacoTree(monacoTree, path.split('/'))),
switchMap(([path]) => from(this.webContainerService.readFile(`${this.project().cwd}/${path}`).catch(() => ''))),
filter(([[path], monacoTree]) => !!path && checkIfPathInMonacoTree(monacoTree, path.split('/'))),
switchMap(([[path]]) => from(this.webContainerService.readFile(`${this.project().cwd}/${path}`).catch(() => ''))),
share()
);

Expand Down Expand Up @@ -238,10 +254,14 @@ export class CodeEditorViewComponent implements OnDestroy {
// Remove link between launch project and terminals
await this.webContainerService.loadProject(project.files, project.commands, project.cwd);
}
await this.loadNewProject();
this.cwd$.next(project?.cwd || '');
this.loadNewProject();
this.cwd$.next(project.cwd);
});
});
this.forceReload.subscribe(async () => {
await this.cleanAllModelsFromMonaco();
await this.loadAllProjectFilesToMonaco();
});
this.form.controls.code.valueChanges.pipe(
distinctUntilChanged(),
skip(1),
Expand All @@ -253,11 +273,20 @@ export class CodeEditorViewComponent implements OnDestroy {
this.loggerService.error('No project found');
return;
}
if (text !== this.fileContent()) {
const { cwd } = this.project();
localStorage.setItem(cwd, JSON.stringify({
...JSON.parse(localStorage.getItem(cwd) || '{}'),
[this.form.controls.file.value!]: text
}));
}
const path = `${this.project().cwd}/${this.form.controls.file.value}`;
this.loggerService.log('Writing file', path);
void this.webContainerService.writeFile(path, text);
});
this.fileContentLoaded$.subscribe((content) => this.form.controls.code.setValue(content));
this.fileContentLoaded$.subscribe((content) => {
this.form.controls.code.setValue(content);
});

// Reload definition types when finishing install
this.webContainerService.runner.dependenciesLoaded$.pipe(
Expand Down Expand Up @@ -338,15 +367,9 @@ export class CodeEditorViewComponent implements OnDestroy {
/**
* Load a new project in global monaco editor and update local form accordingly
*/
private async loadNewProject() {
if (this.project()?.startingFile) {
this.form.controls.file.setValue(this.project().startingFile);
} else {
this.form.controls.file.setValue('');
this.form.controls.code.setValue('');
}
await this.cleanAllModelsFromMonaco();
await this.loadAllProjectFilesToMonaco();
private loadNewProject() {
this.form.controls.file.setValue(this.project().startingFile);
this.forceReload.next();
}

/**
Expand Down Expand Up @@ -382,10 +405,33 @@ export class CodeEditorViewComponent implements OnDestroy {
}
}

public ngOnInit() {
const { cwd } = this.project();
const savedState = localStorage.getItem(cwd);
if (savedState) {
const modal = this.modalService.open(SaveCodeDialogComponent, { backdrop: 'static' });
void modal.result.then((positiveReply) => {
if (positiveReply) {
this.cwdTree$.pipe(take(1)).subscribe(async () => {
const state = JSON.parse(savedState);
await Promise.all(Object.entries<string>(state).map(async ([path, text]) => {
await this.webContainerService.writeFile(`${cwd}/${path}`, text);
}));
this.forceReload.next();
});
} else {
localStorage.removeItem(cwd);
}
});
}
}

/**
* @inheritDoc
*/
public ngOnDestroy() {
this.forceReload.complete();
this.monacoReady.complete();
this.webContainerService.runner.killContainer();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './save-code-dialog.component';
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import {
ComponentFixture,
TestBed,
} from '@angular/core/testing';
import {
SaveCodeDialogComponent,
} from './save-code-dialog.component';

describe('ViewComponent', () => {
let component: SaveCodeDialogComponent;
let fixture: ComponentFixture<SaveCodeDialogComponent>;

beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [SaveCodeDialogComponent]
}).compileComponents();

fixture = TestBed.createComponent(SaveCodeDialogComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});

it('should create', () => {
expect(component).toBeTruthy();
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import {
ChangeDetectionStrategy,
Component,
inject,
} from '@angular/core';
import {
NgbActiveModal,
} from '@ng-bootstrap/ng-bootstrap';

@Component({
selector: 'code-editor-terminal',
changeDetection: ChangeDetectionStrategy.OnPush,
standalone: true,
imports: [],
template: `
<div class="modal-header">
<h2 class="modal-title">Code modifications detected</h2>
</div>
<div class="modal-body">
<p>Do you want to use it or restart from scratch?</p>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-outline-danger me-5" (click)="activeModal.close(false)">Restart from scratch</button>
<button type="button" class="btn btn-primary" ngbAutofocus (click)="activeModal.close(true)">Use saved code</button>
</div>
`
})
export class SaveCodeDialogComponent {
public readonly activeModal = inject(NgbActiveModal);
}

0 comments on commit 07049b5

Please sign in to comment.