Skip to content

Commit e93eedb

Browse files
committed
Add a save button
1 parent 9caf72e commit e93eedb

File tree

11 files changed

+199
-54
lines changed

11 files changed

+199
-54
lines changed

apps/codelab/src/app/admin/admin-routing.module.ts

Lines changed: 15 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -5,41 +5,34 @@ import { UsersComponent } from './users/users.component';
55
import { FeedbackComponent } from './feedback/feedback.component';
66

77
import { CommonModule } from '@angular/common';
8-
import { PresentationListComponent } from './content/presentation-list/presentation-list.component';
9-
import { PreviewComponent } from './content/presentation-editor/preview/preview.component';
10-
import { ContentComponent } from './content/presentation-editor/content.component';
8+
import { NAVIGATION_BASE_URL } from "./content/presentation-editor/services/navigation.service";
9+
1110

1211
const routes = [
1312
{
1413
path: '',
1514
component: AdminComponent,
1615
children: [
17-
{ path: 'users', component: UsersComponent },
18-
{ path: 'feedback', component: FeedbackComponent },
16+
{path: 'users', component: UsersComponent},
17+
{path: 'feedback', component: FeedbackComponent},
1918
],
2019
},
2120
{
2221
path: 'content',
23-
children: [
24-
{
25-
path: '',
26-
component: PresentationListComponent,
27-
},
28-
{
29-
path: ':presentation/:slide/preview',
30-
component: PreviewComponent,
31-
},
32-
{
33-
path: ':presentation/:slide',
34-
component: ContentComponent,
35-
// children: [{ path: '', ContentComponent }],
36-
},
37-
],
22+
loadChildren: () => import('./content/presentation-editor/content.module').then(m => m.ContentModule)
3823
},
3924
];
4025

4126
@NgModule({
42-
imports: [RouterModule.forChild(routes), CommonModule],
27+
imports: [
28+
RouterModule.forChild(routes),
29+
CommonModule,
30+
],
31+
providers: [
32+
33+
{provide: NAVIGATION_BASE_URL, useValue: 'admin/content'},
34+
],
4335
exports: [RouterModule],
4436
})
45-
export class AdminRoutingModule {}
37+
export class AdminRoutingModule {
38+
}

apps/codelab/src/app/admin/admin.module.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@ import { UsersModule } from './users/users.module';
99
import { ContentModule } from './content/presentation-editor/content.module';
1010
import { PreviewModule } from './content/presentation-editor/preview/preview.module';
1111
import { PresentationListModule } from './content/presentation-list/presentation-list.module';
12+
import { initializeApp, provideFirebaseApp } from "@angular/fire/app";
13+
import { environment } from "../../environments/environment";
14+
import { getFirestore, provideFirestore } from "@angular/fire/firestore";
1215

1316
@NgModule({
1417
imports: [
@@ -24,4 +27,5 @@ import { PresentationListModule } from './content/presentation-list/presentation
2427
],
2528
declarations: [AdminComponent],
2629
})
27-
export class AdminModule {}
30+
export class AdminModule {
31+
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import { NgModule } from '@angular/core';
2+
import { RouterModule } from '@angular/router';
3+
import { ContentWrapperComponent } from "./content-wrapper/content-wrapper.component";
4+
import { PresentationListComponent } from "../presentation-list/presentation-list.component";
5+
import { PreviewComponent } from "./preview/preview.component";
6+
import { ContentComponent } from "./content.component";
7+
import { CommonModule } from "@angular/common";
8+
import { NAVIGATION_BASE_URL } from "./services/navigation.service";
9+
10+
11+
const routes = [
12+
{
13+
path: '',
14+
component: ContentWrapperComponent,
15+
children: [
16+
{
17+
path: '',
18+
component: PresentationListComponent,
19+
},
20+
{
21+
path: ':presentation/:slide/preview',
22+
component: PreviewComponent,
23+
},
24+
{
25+
path: ':presentation/:slide',
26+
component: ContentComponent,
27+
},
28+
],
29+
},
30+
];
31+
32+
@NgModule({
33+
imports: [
34+
RouterModule.forChild(routes),
35+
CommonModule,
36+
],
37+
providers: [
38+
{provide: NAVIGATION_BASE_URL, useValue: 'admin/content'},
39+
],
40+
exports: [RouterModule],
41+
})
42+
export class ContentRoutingModule {
43+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
.wrapper {
2+
display: flex;
3+
justify-content: space-between;
4+
}
5+
6+
:host {
7+
padding: 0 16px;
8+
display: block;
9+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<div class="wrapper">
2+
<div>Content</div>
3+
<button mat-button [disabled]="(saveStatus$ | async) !== SaveStatus.UNSAVED" (click)="save()">
4+
Save
5+
<mat-icon *ngIf="(saveStatus$ | async) === SaveStatus.UNSAVED">
6+
radio_button_unchecked
7+
</mat-icon>
8+
<mat-icon *ngIf="(saveStatus$ | async) === SaveStatus.SAVED">
9+
check_circle
10+
</mat-icon>
11+
</button>
12+
</div>
13+
<router-outlet></router-outlet>
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import { Component } from '@angular/core';
2+
import { AutoSaveService, SaveStatus } from "../services/auto-save.service";
3+
4+
@Component({
5+
selector: 'slides-content-wrapper',
6+
templateUrl: './content-wrapper.component.html',
7+
styleUrls: ['./content-wrapper.component.css'],
8+
})
9+
export class ContentWrapperComponent {
10+
readonly saveStatus$ = this.autoSaveService.saveStatus$;
11+
readonly SaveStatus = SaveStatus;
12+
13+
constructor(private readonly autoSaveService: AutoSaveService
14+
) {
15+
autoSaveService.startAutosave();
16+
}
17+
18+
save(){
19+
this.autoSaveService.save();
20+
}
21+
}

apps/codelab/src/app/admin/content/presentation-editor/content.component.ts

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -35,10 +35,12 @@ export class ContentComponent {
3535
);
3636

3737
constructor(
38-
readonly contentService: ContentService,
39-
readonly navigationService: NavigationService,
40-
readonly activeRoute: ActivatedRoute
41-
) {}
38+
private readonly contentService: ContentService,
39+
private readonly navigationService: NavigationService,
40+
private readonly activeRoute: ActivatedRoute
41+
) {
42+
43+
}
4244

4345
addSlide(presentationId: string) {
4446
this.contentService.addSlide(presentationId);

apps/codelab/src/app/admin/content/presentation-editor/content.module.ts

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -9,25 +9,26 @@ import { SidePanelModule } from './side-panel/side-panel.module';
99
import { ContentService } from './services/content.service';
1010
import { MatIconModule } from '@angular/material/icon';
1111
import { MatButtonModule } from '@angular/material/button';
12-
import {
13-
NAVIGATION_BASE_URL,
14-
NavigationService,
15-
} from './services/navigation.service';
12+
import { NavigationService, } from './services/navigation.service';
1613
import { getFirestore, provideFirestore } from "@angular/fire/firestore";
1714
import { initializeApp, provideFirebaseApp } from "@angular/fire/app";
1815
import { environment } from "../../../../environments/environment";
16+
import { ContentRoutingModule } from "./content-routing.module";
17+
import { ContentWrapperComponent } from "./content-wrapper/content-wrapper.component";
18+
import { AutoSaveService } from "./services/auto-save.service";
1919

2020
@NgModule({
21-
declarations: [ContentComponent],
22-
exports: [ContentComponent],
21+
declarations: [ContentComponent, ContentWrapperComponent],
22+
exports: [ContentComponent, ContentWrapperComponent],
2323
providers: [
2424
NavigationService,
2525
ContentService,
26-
{ provide: NAVIGATION_BASE_URL, useValue: 'admin/content' },
26+
AutoSaveService,
2727
],
2828
imports: [
2929
provideFirebaseApp(() => initializeApp(environment.firebaseConfig)),
30-
provideFirestore(()=>getFirestore()),
30+
provideFirestore(() => getFirestore()),
31+
ContentRoutingModule,
3132
CommonModule,
3233
SlideEditorModule,
3334
AngularFirestoreModule,
@@ -38,4 +39,5 @@ import { environment } from "../../../../environments/environment";
3839
MatButtonModule,
3940
],
4041
})
41-
export class ContentModule {}
42+
export class ContentModule {
43+
}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import { Injectable } from '@angular/core';
2+
import { auditTime, skip, take, takeUntil, tap } from "rxjs/operators";
3+
import { ContentService } from "./content.service";
4+
import { BehaviorSubject, ReplaySubject } from "rxjs";
5+
6+
export enum SaveStatus {
7+
UNSAVED = 1,
8+
SAVING,
9+
SAVED
10+
}
11+
12+
@Injectable({
13+
providedIn: 'root'
14+
})
15+
export class AutoSaveService {
16+
readonly onDestroy$ = new ReplaySubject<void>();
17+
private readonly saveStatus = new BehaviorSubject(SaveStatus.SAVING);
18+
readonly saveStatus$ = this.saveStatus.asObservable();
19+
20+
constructor(private readonly contentService: ContentService) {
21+
}
22+
23+
ngOnDestroy() {
24+
this.onDestroy$.next(null);
25+
this.onDestroy$.complete();
26+
}
27+
28+
startAutosave() {
29+
this.contentService.state$
30+
.pipe(
31+
skip(1),
32+
tap(() => {
33+
this.saveStatus.next(SaveStatus.UNSAVED);
34+
}),
35+
auditTime(2500),
36+
takeUntil(this.onDestroy$),
37+
)
38+
.subscribe(() => {
39+
this.save();
40+
});
41+
}
42+
43+
save() {
44+
this.contentService.state$.pipe(
45+
take(1),
46+
takeUntil(this.onDestroy$),
47+
).subscribe((presentations) => {
48+
this.saveStatus.next(SaveStatus.SAVING);
49+
this.contentService.saveState(presentations);
50+
this.saveStatus.next(SaveStatus.SAVED);
51+
});
52+
53+
}
54+
}

apps/codelab/src/app/admin/content/presentation-editor/services/content.service.ts

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { inject, Injectable, OnDestroy } from '@angular/core';
22
import { merge, Subject } from 'rxjs';
3-
import { auditTime, distinctUntilChanged, map, scan, shareReplay, take, takeUntil, } from 'rxjs/operators';
3+
import { distinctUntilChanged, map, scan, shareReplay, take, tap, } from 'rxjs/operators';
44
import { nanoid } from 'nanoid';
55
import { ContentBlock, ContentPresentation } from '../types';
66
import { reducer } from '../reducer';
@@ -12,23 +12,23 @@ const DOC_KEY = 'presentations';
1212

1313
@Injectable({providedIn: 'root'})
1414
export class ContentService implements OnDestroy {
15-
private firestore: Firestore = inject(Firestore);
1615
public readonly currentSlideIndex$ = this.navigationService.currentSlideIndex$;
1716

17+
readonly onDestroy$ = new Subject();
18+
private firestore: Firestore = inject(Firestore);
1819
private readonly presentations = collection(this.firestore, DOC_KEY);
19-
private readonly presentations$ = docData(doc(this.presentations, DOC_KEY))
20+
private readonly presentationDoc = doc(this.presentations, DOC_KEY);
21+
private readonly presentations$ = docData(this.presentationDoc)
2022
.pipe(
2123
distinctUntilChanged((a, b) => {
22-
debugger;
2324
return JSON.stringify(a) === JSON.stringify(b);
2425
}),
2526
map((doc: any) => {
27+
console.log('update');
2628
return doc?.presentations || [];
2729
})
2830
);
29-
3031
private readonly localActions$ = new Subject();
31-
3232
readonly allActions$ = merge(
3333
this.presentations$.pipe(
3434
// TODO(kirjs): actually make it work
@@ -45,25 +45,20 @@ export class ContentService implements OnDestroy {
4545

4646
public readonly state$ = this.allActions$.pipe(
4747
scan((state: ContentPresentation[], action: any) => {
48+
console.log('aAaaa');
4849
return reducer(state, action);
4950
}, {}),
50-
shareReplay(1)
51+
shareReplay(1),
52+
tap(() => {
53+
console.log('kmon');
54+
}),
5155
);
5256

5357
constructor(
5458
private readonly navigationService: NavigationService
5559
) {
56-
// TODO: There should be a better way
57-
58-
this.state$
59-
.pipe(auditTime(2000), takeUntil(this.onDestroy$))
60-
.subscribe((presentations) => {
61-
setDoc(doc(this.presentations, DOC_KEY), {presentations});
62-
});
6360
}
6461

65-
readonly onDestroy$ = new Subject();
66-
6762
uniqueId() {
6863
return nanoid();
6964
}
@@ -240,4 +235,9 @@ export class ContentService implements OnDestroy {
240235

241236
this.dispatch(presentationId, action);
242237
}
238+
239+
saveState(presentations) {
240+
console.log('save');
241+
setDoc(this.presentationDoc, {presentations});
242+
}
243243
}

0 commit comments

Comments
 (0)