Skip to content

Commit

Permalink
feat(daffio): use tabs to render design component guide and API docs
Browse files Browse the repository at this point in the history
  • Loading branch information
griest024 committed Dec 20, 2024
1 parent 1fdd513 commit e45dd8b
Show file tree
Hide file tree
Showing 8 changed files with 287 additions and 9 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,15 @@
</nav>
}

@if (isApiPackage) {
<daffio-api-package [doc]="doc"></daffio-api-package>
} @else {
<daff-article
[innerHtml]="getInnerHtml(doc)">
</daff-article>
}
<ng-content>
@if (isApiPackage) {
<daffio-api-package [doc]="doc"></daffio-api-package>
} @else {
<daff-article
[innerHtml]="getInnerHtml(doc)">
</daff-article>
}
</ng-content>
</div>
@if (isGuideDoc) {
<daffio-docs-table-of-contents
Expand Down
17 changes: 17 additions & 0 deletions apps/daffio/src/app/docs/design/design-routing.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,10 @@ import {
} from '@daffodil/docs-utils';

import { DAFFIO_DOCS_DESIGN_LIST_SIDEBAR_REGISTRATION } from './containers/docs-list/sidebar.provider';
import { DaffioDocsDesignComponentPageComponent } from './pages/component/component';
import { DaffioDocsDesignComponentOverviewPageComponent } from './pages/components-overview/component-overview.component';
import { DaffioDocsDesignOverviewPageComponent } from './pages/overview/overview.component';
import { DaffioDesignComponentDocResolver } from './resolvers/component-doc-resolver.service';
import { DAFF_NAV_SIDEBAR_REGISTRATION } from '../../core/nav/sidebar.provider';
import { DaffioRoute } from '../../core/router/route.type';
import { DaffioDocsPageComponent } from '../pages/docs-page/docs-page.component';
Expand Down Expand Up @@ -49,6 +51,21 @@ export const docsDesignRoutes: Routes = [
path: DAFF_DOC_KIND_PATH_SEGMENT_MAP[DaffDocKind.COMPONENT],
component: DaffioDocsDesignComponentOverviewPageComponent,
},
{
path: 'components',
children: [
<DaffioRoute>{
path: '**',
component: DaffioDocsDesignComponentPageComponent,
resolve: {
doc: DaffioDesignComponentDocResolver,
},
data: {
sidebarMode: DaffSidebarModeEnum.SideFixed,
},
},
],
},
<DaffioRoute>{
path: '**',
component: DaffioDocsPageComponent,
Expand Down
29 changes: 29 additions & 0 deletions apps/daffio/src/app/docs/design/pages/component/component.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<ng-container *ngrxLet="doc$; let doc">
<daffio-doc-viewer [doc]="doc.guide">
<daff-tabs>
<daff-tab>
<daff-tab-label>
Usage
</daff-tab-label>
<daff-tab-panel>
<daff-article
[innerHtml]="getInnerHtml(doc.guide)">
</daff-article>
</daff-tab-panel>
</daff-tab>

<daff-tab>
<daff-tab-label>
API
</daff-tab-label>
<daff-tab-panel>
@for (apiDoc of doc.api; track $index) {
<daff-article
[innerHtml]="getInnerHtml(apiDoc)">
</daff-article>
}
</daff-tab-panel>
</daff-tab>
</daff-tabs>
</daffio-doc-viewer>
</ng-container>
39 changes: 39 additions & 0 deletions apps/daffio/src/app/docs/design/pages/component/component.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import {
provideHttpClient,
withInterceptorsFromDi,
} from '@angular/common/http';
import { provideHttpClientTesting } from '@angular/common/http/testing';
import {
waitForAsync,
ComponentFixture,
TestBed,
} from '@angular/core/testing';
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
import { RouterTestingModule } from '@angular/router/testing';

import { DaffioDocsDesignComponentPageComponent } from './docs-list.component';

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

beforeEach(waitForAsync(() => {
TestBed.configureTestingModule({
imports: [DaffioDocsDesignComponentPageComponent,
RouterTestingModule,
NoopAnimationsModule],
providers: [provideHttpClient(withInterceptorsFromDi()), provideHttpClientTesting()],
})
.compileComponents();
}));

beforeEach(() => {
fixture = TestBed.createComponent(DaffioDocsDesignComponentPageComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});

it('should create', () => {
expect(component).toBeTruthy();
});
});
55 changes: 55 additions & 0 deletions apps/daffio/src/app/docs/design/pages/component/component.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import {
ChangeDetectionStrategy,
Component,
OnInit,
} from '@angular/core';
import {
DomSanitizer,
SafeHtml,
} from '@angular/platform-browser';
import { ActivatedRoute } from '@angular/router';
import { LetDirective } from '@ngrx/component';
import {
map,
Observable,
} from 'rxjs';

import { DAFF_ARTICLE_COMPONENTS } from '@daffodil/design/article';
import { DAFF_TABS_COMPONENTS } from '@daffodil/design/tabs';
import {
DaffDoc,
DaffGuideDoc,
} from '@daffodil/docs-utils';

import { DaffioDocViewerModule } from '../../../components/doc-viewer/doc-viewer.module';
import { DaffioDesignComponentDoc } from '../../resolvers/component-doc-resolver.service';

@Component({
selector: 'daffio-docs-design-component-page',
templateUrl: './component.html',
changeDetection: ChangeDetectionStrategy.OnPush,
standalone: true,
imports: [
LetDirective,
DAFF_TABS_COMPONENTS,
DAFF_ARTICLE_COMPONENTS,
DaffioDocViewerModule,
],
})
export class DaffioDocsDesignComponentPageComponent implements OnInit {
doc$: Observable<DaffioDesignComponentDoc>;

constructor(
private route: ActivatedRoute,
private sanitizer: DomSanitizer,
) {}

ngOnInit() {
this.doc$ = this.route.data.pipe(map((data: { doc: DaffioDesignComponentDoc }) => data.doc));
}

getInnerHtml(doc: DaffDoc | DaffGuideDoc): SafeHtml {
//It is necessary to bypass the default angular sanitization to keep id tags in the injected html. These id tags are used for fragment routing.
return this.sanitizer.bypassSecurityTrustHtml(doc.contents);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import { TestBed } from '@angular/core/testing';
import {
RouterStateSnapshot,
Router,
} from '@angular/router';
import { RouterTestingModule } from '@angular/router/testing';
import { cold } from 'jasmine-marbles';
import {
of,
Observable,
throwError,
} from 'rxjs';

import { DaffDoc } from '@daffodil/docs-utils';

import { DocsResolver } from './component-doc-resolver.service';
import { DaffioDocsServiceInterface } from '../services/docs-service.interface';
import { DaffioDocsService } from '../services/docs.service';
import { DaffioDocsFactory } from '../testing/factories/docs.factory';

describe('DocsResolver', () => {
let resolver: DocsResolver;
let docsService: DaffioDocsService;
let router: Router;

const doc = new DaffioDocsFactory().create();
const stubDocService: DaffioDocsServiceInterface = {
get: (path: string): Observable<DaffDoc> => of(doc),
};

beforeEach(() => {
TestBed.configureTestingModule({
imports: [
RouterTestingModule,
],
providers: [
{ provide: DaffioDocsService, useValue: stubDocService },
],
});

router = TestBed.inject(Router);
docsService = TestBed.inject(DaffioDocsService);
resolver = TestBed.inject(DocsResolver);
});

it('should be created', () => {
expect(resolver).toBeTruthy();
});

it('should complete with a doc', () => {
const expected = cold('(a|)', { a: doc });
expect(resolver.resolve(null, <RouterStateSnapshot>{ url: 'my/path' })).toBeObservable(expected);
});

describe('if the doc doesn\'t exist (the doc service errors)', () => {
beforeEach(() => {
spyOn(docsService, 'get').and.returnValue(throwError('error'));
spyOn(router, 'navigate');
});

it('should resolve with an empty observable', () => {
const expected = cold('(|)');
expect(resolver.resolve(null, <RouterStateSnapshot>{ url: 'my/path' })).toBeObservable(expected);
});

it('should redirect to the 404 page', () => {
resolver.resolve(null, <RouterStateSnapshot>{ url: 'my/path' }).subscribe();
expect(router.navigate).toHaveBeenCalledWith(['/404'], { skipLocationChange: true });
});
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import { Injectable } from '@angular/core';
import {
ActivatedRouteSnapshot,
RouterStateSnapshot,
Router,
} from '@angular/router';
import {
Observable,
EMPTY,
combineLatest,
} from 'rxjs';
import {
take,
catchError,
switchMap,
withLatestFrom,
map,
} from 'rxjs/operators';

import {
daffUriTruncateLeadingSlash,
daffUriTruncateQueryFragment,
} from '@daffodil/core/routing';
import {
DaffApiDoc,
DaffPackageGuideDoc,
} from '@daffodil/docs-utils';

import { DaffioDocsService } from '../../services/docs.service';

export interface DaffioDesignComponentDoc {
guide: DaffPackageGuideDoc;
api: Array<DaffApiDoc>;
}

@Injectable({
providedIn: 'root',
})
export class DaffioDesignComponentDocResolver {

constructor(private docService: DaffioDocsService, private router: Router) { }

resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<DaffioDesignComponentDoc> {
return this.docService
//remove any route fragment and initial slash from the route.
.get<DaffPackageGuideDoc>(daffUriTruncateLeadingSlash(daffUriTruncateQueryFragment(state.url)))
.pipe(
take(1),
switchMap((packageDoc) =>
combineLatest(packageDoc.symbols?.map((symbol) =>
this.docService.get<DaffApiDoc>(symbol)),
).pipe(
map((api) => ({
guide: packageDoc,
api,
})),
),
),
catchError(() => {
this.router.navigate(['/404'], { skipLocationChange: true });
return EMPTY;
}),
);
}
}
4 changes: 2 additions & 2 deletions apps/daffio/src/app/docs/services/docs.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ export class DaffioDocsService<T extends DaffDoc = DaffDoc> implements DaffioDoc
@Inject(DAFFIO_DOCS_PATH_TOKEN) private docsPath: string,
) {}

get(path: string): Observable<T> {
return this.fetchAsset.fetch<T>(`${this.docsPath}/${crossOsFilename(path)}.json`);
get<R extends T = T>(path: string): Observable<R> {
return this.fetchAsset.fetch<R>(`${this.docsPath}/${crossOsFilename(path)}.json`);
}
}

0 comments on commit e45dd8b

Please sign in to comment.