docs(docs-infra): add open in StackBlitz button to code editor (#57018)

PR Close #57018
This commit is contained in:
Tomek Sułkowski 2024-07-17 13:37:11 +02:00 committed by Andrew Scott
parent d3e14b35d3
commit edbb403171
7 changed files with 105 additions and 3 deletions

View file

@ -78,6 +78,7 @@ APPLICATION_DEPS = [
"@npm//@lezer/highlight",
"@npm//@lezer/javascript",
"@npm//@lezer/common",
"@npm//@stackblitz/sdk",
"@npm//@xterm/xterm",
"@npm//@xterm/addon-fit",
"@npm//algoliasearch",

View file

@ -65,6 +65,14 @@
</button>
</div>
<button
class="adev-editor-download-button"
type="button"
(click)="openCurrentCodeInStackBlitz()"
aria-label="Open current code in editor in StackBlitz"
>
<docs-icon>launch</docs-icon>
</button>
<button
class="adev-editor-download-button"
type="button"

View file

@ -6,7 +6,7 @@
* found in the LICENSE file at https://angular.dev/license
*/
import {NgFor, NgIf} from '@angular/common';
import {Location, NgFor, NgIf} from '@angular/common';
import {
AfterViewInit,
ChangeDetectionStrategy,
@ -20,6 +20,7 @@ import {
} from '@angular/core';
import {takeUntilDestroyed} from '@angular/core/rxjs-interop';
import {MatTabGroup, MatTabsModule} from '@angular/material/tabs';
import {Title} from '@angular/platform-browser';
import {debounceTime, map} from 'rxjs';
import {TerminalType} from '../terminal/terminal-handler.service';
@ -28,6 +29,7 @@ import {EmbeddedTutorialManager} from '../embedded-tutorial-manager.service';
import {CodeMirrorEditor} from './code-mirror-editor.service';
import {DiagnosticWithLocation, DiagnosticsState} from './services/diagnostics-state.service';
import {DownloadManager} from '../download-manager.service';
import {StackBlitzOpener} from '../stackblitz-opener.service';
import {ClickOutside, IconComponent} from '@angular/docs';
export const REQUIRED_FILES = new Set([
@ -36,6 +38,8 @@ export const REQUIRED_FILES = new Set([
'src/app/app.component.ts',
]);
const ANGULAR_DEV = 'https://angular.dev';
@Component({
selector: 'docs-tutorial-code-editor',
standalone: true,
@ -73,6 +77,9 @@ export class CodeEditor implements AfterViewInit, OnDestroy {
private readonly codeMirrorEditor = inject(CodeMirrorEditor);
private readonly diagnosticsState = inject(DiagnosticsState);
private readonly downloadManager = inject(DownloadManager);
private readonly stackblitzOpener = inject(StackBlitzOpener);
private readonly title = inject(Title);
private readonly location = inject(Location);
private readonly embeddedTutorialManager = inject(EmbeddedTutorialManager);
private readonly errors$ = this.diagnosticsState.diagnostics$.pipe(
@ -110,6 +117,16 @@ export class CodeEditor implements AfterViewInit, OnDestroy {
this.codeMirrorEditor.disable();
}
async openCurrentCodeInStackBlitz(): Promise<void> {
const title = this.title.getTitle();
const path = this.location.path();
const editorUrl = `${ANGULAR_DEV}${path}`;
const description = `Angular.dev example generated from [${editorUrl}](${editorUrl})`;
await this.stackblitzOpener.openCurrentSolutionInStackBlitz({title, description});
}
async downloadCurrentCodeEditorState(): Promise<void> {
const name = this.embeddedTutorialManager.tutorialId();
await this.downloadManager.downloadCurrentStateOfTheSolution(name);

View file

@ -0,0 +1,24 @@
/*!
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.dev/license
*/
import {TestBed} from '@angular/core/testing';
import {StackBlitzOpener} from './stackblitz-opener.service';
describe('StackBlitzOpener', () => {
let service: StackBlitzOpener;
beforeEach(() => {
TestBed.configureTestingModule({});
service = TestBed.inject(StackBlitzOpener);
});
it('should be created', () => {
expect(service).toBeTruthy();
});
});

View file

@ -0,0 +1,46 @@
/*!
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.dev/license
*/
import {EnvironmentInjector, Injectable, inject} from '@angular/core';
import sdk, {Project, ProjectFiles} from '@stackblitz/sdk';
import {injectAsync} from '../core/services/inject-async';
@Injectable({
providedIn: 'root',
})
export class StackBlitzOpener {
private readonly environmentInjector = inject(EnvironmentInjector);
/**
* Generate a StackBlitz project from the current state of the solution in the EmbeddedEditor
*/
async openCurrentSolutionInStackBlitz(
projectMetadata: Pick<Project, 'title' | 'description'>,
): Promise<void> {
const nodeRuntimeSandbox = await injectAsync(this.environmentInjector, () =>
import('./node-runtime-sandbox.service').then((c) => c.NodeRuntimeSandbox),
);
const runtimeFiles = await nodeRuntimeSandbox.getSolutionFiles();
const stackblitzProjectFiles: ProjectFiles = {};
runtimeFiles.forEach((file) => {
// Leading slashes are incompatible with StackBlitz SDK they are removed
const path = file.path.replace(/^\//, '');
stackblitzProjectFiles[path] =
typeof file.content !== 'string' ? new TextDecoder().decode(file.content) : file.content;
});
sdk.openProject({
...projectMetadata,
template: 'node',
files: stackblitzProjectFiles,
});
}
}

View file

@ -48,10 +48,10 @@
},
"// 1": "dependencies are used locally and by bazel",
"dependencies": {
"@angular/build": "18.1.0-next.1",
"@angular-devkit/build-angular": "18.1.0-next.1",
"@angular-devkit/core": "18.1.0-next.1",
"@angular-devkit/schematics": "18.1.0-next.1",
"@angular/build": "18.1.0-next.1",
"@angular/cdk": "18.1.0",
"@angular/cli": "18.1.0-next.1",
"@angular/material": "18.1.0",
@ -72,6 +72,7 @@
"@rollup/plugin-commonjs": "^26.0.0",
"@rollup/plugin-node-resolve": "^13.0.4",
"@schematics/angular": "18.1.0-next.1",
"@stackblitz/sdk": "^1.11.0",
"@types/angular": "^1.6.47",
"@types/babel__core": "7.20.5",
"@types/babel__generator": "7.6.8",
@ -91,8 +92,8 @@
"@types/shelljs": "^0.8.6",
"@types/systemjs": "6.13.5",
"@types/yargs": "^17.0.3",
"@xterm/xterm": "^5.5.0",
"@xterm/addon-fit": "^0.10.0",
"@xterm/xterm": "^5.5.0",
"algoliasearch": "^4.23.3",
"angular-1.5": "npm:angular@1.5",
"angular-1.6": "npm:angular@1.6",

View file

@ -4117,6 +4117,11 @@
resolved "https://registry.yarnpkg.com/@socket.io/component-emitter/-/component-emitter-3.1.2.tgz#821f8442f4175d8f0467b9daf26e3a18e2d02af2"
integrity sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==
"@stackblitz/sdk@^1.11.0":
version "1.11.0"
resolved "https://registry.yarnpkg.com/@stackblitz/sdk/-/sdk-1.11.0.tgz#ba30c837decca221ce8d605ff768a774c0f92f89"
integrity sha512-DFQGANNkEZRzFk1/rDP6TcFdM82ycHE+zfl9C/M/jXlH68jiqHWHFMQURLELoD8koxvu/eW5uhg94NSAZlYrUQ==
"@szmarczak/http-timer@^4.0.5":
version "4.0.6"
resolved "https://registry.yarnpkg.com/@szmarczak/http-timer/-/http-timer-4.0.6.tgz#b4a914bb62e7c272d4e5989fe4440f812ab1d807"