[A] stroy.service @WIP, huddle.compo - story CRUD @WIP

This commit is contained in:
Jan 2020-10-29 23:52:33 +01:00
parent 8509578c74
commit 412a542e03
8 changed files with 270 additions and 9 deletions

View file

@ -0,0 +1,16 @@
import { TestBed } from '@angular/core/testing';
import { StoryService } from './story.service';
describe('StoryService', () => {
let service: StoryService;
beforeEach(() => {
TestBed.configureTestingModule({});
service = TestBed.inject(StoryService);
});
it('should be created', () => {
expect(service).toBeTruthy();
});
});

View file

@ -0,0 +1,181 @@
import { Injectable } from '@angular/core';
import { BehaviorSubject, Observable, of, Subject } from 'rxjs';
import { Story } from './story';
import { SupaService } from './supa.service';
@Injectable({
providedIn: 'root'
})
export class StoryService {
// storyMap: Map<number, Story> = new Map();
storyMap = {};
stories: BehaviorSubject<any> = new BehaviorSubject([]);
subscribedStandUpIds: number[] = [];
isListening: boolean = false;
constructor(
private supa: SupaService,
) { }
/**
* Get Storys from local store.
* Requests data if store is emtpy.
* @param standup_id
* @returns Observable<Story[]>
*/
getStories(standup_id: number): Observable<Story[]> {
if (!this.subscribedStandUpIds.includes(standup_id)) {
this.subscribeToStories(standup_id);
console.log('getStories - REFRESH', standup_id)
const subject: Subject<Story[]> = new Subject();
this.supa.client.from<Story>('story').select()
.filter(<never>'standup_id', 'eq', standup_id)
.then(data => {
this.updateStore(data.body);
subject.next(data.body);
subject.complete();
})
.catch(error => {
subject.error(error);
subject.complete();
});
return subject.asObservable();
} else {
console.log('getStories - LOCAL', standup_id)
const stories = Object.values<Story>(this.storyMap).filter(e => e.standup_id === standup_id)
console.log('filteredStories', stories)
return of(stories);
}
}
/**
* Listen to realtime events from story db.
*/
subscribeToStories(standup_id: number) {
// TODO check for this.subscribedStandUpIds.includes(standup_id)
if (!this.isListening) {
this.subscribedStandUpIds.push(standup_id);
this.supa.client.from<Story>('story').on('*', payload => {
console.log('subscribeToStories - REALTIME EVENT', payload)
if ((payload.eventType === 'INSERT') || (payload.eventType === 'UPDATE')) {
this.storyMap[payload.new.id] = payload.new;
} else {
delete this.storyMap[payload.old.id];
}
this.next();
}).subscribe();
}
}
/**
* Requests up to date data from API.
* Returns the local copy.
* @returns Observable<Story>
*/
// refreshStorys(): Observable<Story[]> {
// this.subscribeToStories();
// this.supa.client.from<Story>('story').select()
// .then(stories => this.updateStore(stories.body))
// .catch(error => console.log('Error: ', error));
// return this.stories.asObservable();
// }
/**
* Update the local store with provided stories
* @param stories
*/
updateStore(stories: Story[]) {
stories.forEach(e => {
// this.storyMap.set(e.id, e);
this.storyMap[e.id] = e;
});
this.next();
}
/**
* Emits local store.
*/
next = () => this.stories.next(Object.values(this.storyMap));
/**
* Retrieve story from local store.
* @param id
*/
getOne(id: number) {
if (this.storyMap[id]) {
return of(this.storyMap[id]);
} else {
const subject: Subject<Story> = new Subject();
this.supa.client.from<Story>('story').select('id, name, description')
.filter(<never>'id', 'eq', id)
.then(data => {
this.updateStore([data.body[0]]);
subject.next(data.body[0]);
subject.complete();
})
.catch(error => {
subject.error(error);
subject.complete();
});
return subject.asObservable();
}
}
/**
*
* @param story
*/
updateOne(story: Story): Observable<Story> {
const subject: Subject<Story> = new Subject();
this.supa.client.from<Story>('story').update(story)
.match({ id: story.id })
.then(data => {
subject.next(data.body[0]);
subject.complete();
})
.catch(error => {
subject.error(error);
subject.complete();
});
return subject.asObservable();
}
/**
* Removes one story from db.
* @param story
*/
deleteOne(story: Story): Observable<Story> {
const subject: Subject<Story> = new Subject();
this.supa.client.from<Story>('story').delete()
.match({ id: story.id })
.then(data => {
subject.next(story);
subject.complete();
})
.catch(error => {
subject.error(error);
subject.complete();
});
return subject.asObservable();
}
/**
* Creates a story on the db.
* @param story
*/
addOne(story: Story): Observable<Story> {
const subject: Subject<Story> = new Subject();
this.supa.client.from<Story>('story').insert(story)
.then(data => {
subject.next(data.body[0]);
subject.complete();
})
.catch(error => {
subject.error(error);
subject.complete();
});
return subject.asObservable();
}
}

View file

@ -0,0 +1,13 @@
export class Story {
id: number;
standup_id: number;
user_id: string
base64: string;
created_at: string | Date;
constructor(user_id: string, standup_id: number, base64: string) {
this.user_id = user_id;
this.standup_id = standup_id;
this.base64 = base64;
}
}

View file

@ -7,6 +7,7 @@ import { FormsModule } from '@angular/forms';
import { SupaService } from './api/supabase/supa.service';
import { StandupService } from './api/supabase/standup.service';
import { StoryService } from './api/supabase/story.service';
import { AuthGuard } from './authguard.service';
import { AppComponent } from "./app.component";
@ -40,6 +41,7 @@ import { DashboardComponent } from './dashboard/dashboard.component';
providers: [
SupaService,
StandupService,
StoryService,
AuthGuard,
],
bootstrap: [

View file

@ -10,7 +10,7 @@
</div>
</div>
<div class="col-12">
<ah-recorder></ah-recorder>
<ah-recorder (recordingEnded)="uploadStory($event)"></ah-recorder>
</div>
<div *ngFor="let user of users" class="col p-3">
<div class="text-center" (click)="playStory(user)">

View file

@ -1,8 +1,11 @@
import { Component, OnInit, ViewChild } from "@angular/core";
import { ActivatedRoute } from '@angular/router';
import { NgbModal, NgbModalRef } from "@ng-bootstrap/ng-bootstrap";
import { SupabaseAuthUser } from '@supabase/supabase-js';
import { StandUp } from '../api/supabase/standup';
import { StandupService } from '../api/supabase/standup.service';
import { Story } from '../api/supabase/story';
import { StoryService } from '../api/supabase/story.service';
import { SupaService } from '../api/supabase/supa.service';
@Component({
@ -37,10 +40,13 @@ export class HuddleComponent implements OnInit {
];
selectedUser;
standup: StandUp;
stories: Story[] = [];
constructor(
private modalService: NgbModal,
private standupService: StandupService,
private storyService: StoryService,
private supaService: SupaService,
private route: ActivatedRoute,
) {}
@ -52,6 +58,7 @@ export class HuddleComponent implements OnInit {
data => {
console.log(data);
this.standup = data;
this.loadStories(data.id).subscribe(stories => this.stories = stories);
},
error => {
console.error(error);
@ -133,7 +140,26 @@ export class HuddleComponent implements OnInit {
error => {
console.error('Failed', error);
}
)
);
}
async uploadStory(base64: string) {
console.log('uploadStory', event);
const user: SupabaseAuthUser = await this.supaService.client.auth.user();
// TODO ID muss in DB noch autoincremented werden.
const story = new Story(user.id, this.standup.id, base64);
this.storyService.addOne(story).subscribe(
data => {
console.log('Success', data);
},
error => {
console.error('Failed', error);
}
);
}
loadStories(id: number) {
return this.storyService.getStories(id);
}
}

View file

@ -1,11 +1,21 @@
<button class="btn btn-primary" (click)="openModal()">Record</button>
<button class="btn btn-primary" (click)="openModal()">
<svg width="20" height="20" style="height: 20px; width: 20px; margin-top: -4px;" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d="M12 15C13.6569 15 15 13.6569 15 12C15 10.3431 13.6569 9 12 9C10.3431 9 9 10.3431 9 12C9 13.6569 10.3431 15 12 15Z"
fill="currentColor" />
<path fill-rule="evenodd" clip-rule="evenodd"
d="M22 12C22 17.5228 17.5228 22 12 22C6.47715 22 2 17.5228 2 12C2 6.47715 6.47715 2 12 2C17.5228 2 22 6.47715 22 12ZM20 12C20 16.4183 16.4183 20 12 20C7.58172 20 4 16.4183 4 12C4 7.58172 7.58172 4 12 4C16.4183 4 20 7.58172 20 12Z"
fill="currentColor" />
</svg>
Record
</button>
<ng-template #content let-modal>
<div class="modal-header">
<strong>Record a stoy</strong>
<span class="float-right cursor-pointer" (click)="modal.dismiss('Cross click')">
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<svg width="20" height="20" style="height: 20px; width: 20px; margin-top: -4px;" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d="M6.2253 4.81108C5.83477 4.42056 5.20161 4.42056 4.81108 4.81108C4.42056 5.20161 4.42056 5.83477 4.81108 6.2253L10.5858 12L4.81114 17.7747C4.42062 18.1652 4.42062 18.7984 4.81114 19.1889C5.20167 19.5794 5.83483 19.5794 6.22535 19.1889L12 13.4142L17.7747 19.1889C18.1652 19.5794 18.7984 19.5794 19.1889 19.1889C19.5794 18.7984 19.5794 18.1652 19.1889 17.7747L13.4142 12L19.189 6.2253C19.5795 5.83477 19.5795 5.20161 19.189 4.81108C18.7985 4.42056 18.1653 4.42056 17.7748 4.81108L12 10.5858L6.2253 4.81108Z"
fill="currentColor" />
@ -23,7 +33,7 @@
d="M22 12C22 17.5228 17.5228 22 12 22C6.47715 22 2 17.5228 2 12C2 6.47715 6.47715 2 12 2C17.5228 2 22 6.47715 22 12ZM20 12C20 16.4183 16.4183 20 12 20C7.58172 20 4 16.4183 4 12C4 7.58172 7.58172 4 12 4C16.4183 4 20 7.58172 20 12Z"
fill="currentColor" />
</svg>
Record
Start recording
</button>
<button *ngIf="isRecording" class="btn btn-danger" (click)="stopRecording()">Stop</button>
</div>

View file

@ -1,4 +1,4 @@
import { Component, OnInit, ViewChild } from '@angular/core';
import { Component, EventEmitter, OnInit, Output, ViewChild } from '@angular/core';
import { NgbModal, NgbModalRef } from "@ng-bootstrap/ng-bootstrap";
import * as rrtc from 'recordrtc';
@ -10,6 +10,7 @@ import * as rrtc from 'recordrtc';
export class RecorderComponent implements OnInit {
@ViewChild("content") content;
@ViewChild("activeStory") video;
@Output('recordingEnded') recordingEnded: EventEmitter<string> = new EventEmitter();
stream: MediaStream;
recorder: rrtc.RecordRTCPromisesHandler;
modal: NgbModalRef;
@ -46,12 +47,24 @@ export class RecorderComponent implements OnInit {
}
async stopRecording() {
const that = this;
this.isRecording = false;
await this.recorder.stopRecording();
let blob = await this.recorder.getBlob();
// TODO upload instead
rrtc.invokeSaveAsDialog(blob, 'Recorded-Video.webm');
this.modal.close();
// read as b64
const reader = new FileReader();
reader.readAsDataURL(blob);
reader.onloadend = function() {
const base64data:string = String(reader.result);
console.log(base64data);
that.recordingEnded.emit(base64data);
that.modal.close();
}
const handleError = (error) => {
console.error(error);
}
reader.onerror = handleError;
reader.onabort = handleError;
}
}