[U] huddle.compo upload stories, get stories for correct standup

This commit is contained in:
Jan 2020-10-30 13:16:40 +01:00
parent 412a542e03
commit ccde75f72a
5 changed files with 101 additions and 92 deletions

View file

@ -1,5 +1,6 @@
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { BehaviorSubject, Observable, of, Subject } from 'rxjs'; import { BehaviorSubject, Observable, of, Subject } from 'rxjs';
import { map } from 'rxjs/operators';
import { Story } from './story'; import { Story } from './story';
import { SupaService } from './supa.service'; import { SupaService } from './supa.service';
@ -27,25 +28,20 @@ export class StoryService {
if (!this.subscribedStandUpIds.includes(standup_id)) { if (!this.subscribedStandUpIds.includes(standup_id)) {
this.subscribeToStories(standup_id); this.subscribeToStories(standup_id);
console.log('getStories - REFRESH', standup_id) console.log('getStories - REFRESH', standup_id)
const subject: Subject<Story[]> = new Subject();
this.supa.client.from<Story>('story').select() this.supa.client.from<Story>('story').select()
.filter(<never>'standup_id', 'eq', standup_id) .filter(<never>'standup_id', 'eq', standup_id)
.then(data => { .then(data => {
this.updateStore(data.body); this.updateStore(data.body);
subject.next(data.body);
subject.complete();
}) })
.catch(error => { .catch(error => {
subject.error(error); console.error(error);
subject.complete();
}); });
return subject.asObservable();
} else { } else {
console.log('getStories - LOCAL', standup_id) 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);
} }
return this.stories.asObservable().pipe(
map(stories => stories.filter(e => e.standup_id === standup_id))
);
} }
/** /**
@ -89,13 +85,17 @@ export class StoryService {
// this.storyMap.set(e.id, e); // this.storyMap.set(e.id, e);
this.storyMap[e.id] = e; this.storyMap[e.id] = e;
}); });
console.log('update stories', this.storyMap)
this.next(); this.next();
} }
/** /**
* Emits local store. * Emits local store.
*/ */
next = () => this.stories.next(Object.values(this.storyMap)); next = () => {
console.log('next', Object.values(this.storyMap))
this.stories.next(Object.values(this.storyMap))
};
/** /**
* Retrieve story from local store. * Retrieve story from local store.

View file

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

View file

@ -12,12 +12,12 @@
<div class="col-12"> <div class="col-12">
<ah-recorder (recordingEnded)="uploadStory($event)"></ah-recorder> <ah-recorder (recordingEnded)="uploadStory($event)"></ah-recorder>
</div> </div>
<div *ngFor="let user of users" class="col p-3"> <div *ngFor="let story of stories" class="col p-3">
<div class="text-center" (click)="playStory(user)"> <div class="text-center" (click)="playStory(story)">
<div class="storyImage" style="background-image: url({{user.image}})" <div class="storyImage" style="background-image: url('https://placeimg.com/640/480/people')"
[ngClass]="{'hasStory': user.story_link && user.submit_time}"></div> [ngClass]="{'hasStory': story.id && story.src}"></div>
<p class="mt-2 mb-0"><strong>{{user.name}}</strong></p> <p class="mt-2 mb-0"><strong>{{story.user_id}}</strong></p>
<p><small>{{user.submit_time | date :'dd.MM. HH:mm' }}</small></p> <p><small>{{story.created_at | date :'dd.MM. HH:mm' }}</small></p>
</div> </div>
</div> </div>
</div> </div>
@ -46,9 +46,9 @@
</button> </button>
</div> </div>
<p class="text-center m-0"> <p class="text-center m-0">
<strong>{{selectedUser.name}} - <strong>{{selectedStory.user_id}} - <small>{{selectedStory.created_at | date :'dd.MM. HH:mm' }}</small></strong>
<small>{{selectedUser.submit_time | date :'dd.MM. HH:mm' }}</small></strong>
</p> </p>
<video [src]="selectedUser.story_link" (ended)="nextUser(modal)" autoplay></video> <video [src]="base64ToSafeURL(selectedStory.src)" (ended)="nextUser(modal)" autoplay>
</video>
</div> </div>
</ng-template> </ng-template>

View file

@ -1,46 +1,27 @@
import { Component, OnInit, ViewChild } from "@angular/core"; import { Component, OnDestroy, OnInit, ViewChild } from "@angular/core";
import { DomSanitizer, SafeUrl } from '@angular/platform-browser';
import { ActivatedRoute } from '@angular/router'; import { ActivatedRoute } from '@angular/router';
import { NgbModal, NgbModalRef } from "@ng-bootstrap/ng-bootstrap"; import { NgbModal, NgbModalRef } from "@ng-bootstrap/ng-bootstrap";
import { SupabaseAuthUser } from '@supabase/supabase-js'; import { SupabaseAuthUser } from '@supabase/supabase-js';
import { Subject } from 'rxjs';
import { take, takeUntil } from 'rxjs/operators';
import { StandUp } from '../api/supabase/standup'; import { StandUp } from '../api/supabase/standup';
import { StandupService } from '../api/supabase/standup.service'; import { StandupService } from '../api/supabase/standup.service';
import { Story } from '../api/supabase/story'; import { Story } from '../api/supabase/story';
import { StoryService } from '../api/supabase/story.service'; import { StoryService } from '../api/supabase/story.service';
import { SupaService } from '../api/supabase/supa.service'; import { SupaService } from '../api/supabase/supa.service';
@Component({ @Component({
selector: 'app-huddle', selector: 'app-huddle',
templateUrl: './huddle.component.html', templateUrl: './huddle.component.html',
styleUrls: ['./huddle.component.scss'] styleUrls: ['./huddle.component.scss']
}) })
export class HuddleComponent implements OnInit { export class HuddleComponent implements OnInit, OnDestroy {
@ViewChild("content") content; @ViewChild("content") content;
@ViewChild("activeStory") video; @ViewChild("activeStory") video;
users = [
{
name: "Jan",
image: "https://www.supercardating.com/doc/image.rhtm/profile-pic2.jpg",
submit_time: 1601655386668,
story_link: "https://erjb.s3.nl-ams.scw.cloud/ttk_beagle.mp4"
},
{
name: "Bob",
image:
"https://external-content.duckduckgo.com/iu/?u=https%3A%2F%2Ftse1.mm.bing.net%2Fth%3Fid%3DOIP.4oYqJqInuQd2TAlPPdggLgHaHa%26pid%3DApi&f=1",
submit_time: null,
story_link: "https://erjb.s3.nl-ams.scw.cloud/ttk_golden.mp4"
},
{
name: "Angela",
image:
"https://writestylesonline.com/wp-content/uploads/2019/01/What-To-Wear-For-Your-Professional-Profile-Picture-or-Headshot.jpg",
submit_time: 1601655386668,
story_link: "https://erjb.s3.nl-ams.scw.cloud/ttk_frenchie.mp4"
}
];
selectedUser;
standup: StandUp; standup: StandUp;
selectedStory: Story;
stories: Story[] = []; stories: Story[] = [];
unsubscribe: Subject<boolean> = new Subject();
constructor( constructor(
private modalService: NgbModal, private modalService: NgbModal,
@ -48,17 +29,23 @@ export class HuddleComponent implements OnInit {
private storyService: StoryService, private storyService: StoryService,
private supaService: SupaService, private supaService: SupaService,
private route: ActivatedRoute, private route: ActivatedRoute,
private sanitizer: DomSanitizer,
) {} ) {}
ngOnInit() { ngOnInit() {
this.route.params.subscribe( this.route.params.subscribe(
params => { params => {
console.warn('PARAMS', params)
this.reset();
if (params.id) { if (params.id) {
this.standupService.getOne(params.id).subscribe( this.standupService.getOne(params.id).subscribe(
data => { data => {
console.log(data); console.log(data);
this.standup = data; this.standup = data;
this.loadStories(data.id).subscribe(stories => this.stories = stories); this.loadStories(data.id).subscribe(stories => {
console.log('GOT STORIES', stories);
this.stories = stories;
});
}, },
error => { error => {
console.error(error); console.error(error);
@ -67,62 +54,70 @@ export class HuddleComponent implements OnInit {
} }
} }
) )
this.unsubscribe.subscribe(data => console.warn('UNSUBSCRIBE'))
} }
playStory(user) { ngOnDestroy() {
if (!user.submit_time || !user.story_link) { console.error('HUDDLE COMP DESTROYED')
this.unsubscribe.next(true);
this.unsubscribe.complete();
}
reset() {
this.stories = [];
delete this.standup;
delete this.selectedStory;
this.unsubscribe.next(true);
}
playStory(story) {
if (!story.id || !story.src) {
return; return;
} }
this.modalService.open(this.content, { centered: true }); this.modalService.open(this.content, { centered: true });
this.selectedUser = user; this.selectedStory = story;
} }
prevUser(modal: NgbModalRef) { prevUser(modal: NgbModalRef) {
let index: number = this.users.findIndex( let index: number = this.stories.findIndex(
(u) => u.name === this.selectedUser.name (u) => u.id === this.selectedStory.id
); );
if (index < 1) { if (index < 1) {
modal.close(); modal.close();
return; return;
} }
while ( while (!this.stories[index - 1].id) {
!this.users[index - 1].story_link ||
!this.users[index - 1].submit_time
) {
index--; index--;
if (index === 0) { if (index === 0) {
modal.close(); modal.close();
return; return;
} }
} }
this.selectedUser = this.users[index - 1]; this.selectedStory = this.stories[index - 1];
} }
nextUser(modal: NgbModalRef) { nextUser(modal: NgbModalRef) {
let index: number = this.users.findIndex( let index: number = this.stories.findIndex(
(u) => u.name === this.selectedUser.name (u) => u.id === this.selectedStory.id
); );
if (index === -1 || index === this.users.length - 1) { if (index === -1 || index === this.stories.length - 1) {
modal.close(); modal.close();
return; return;
} }
while ( while (!this.stories[index + 1].id) {
!this.users[index + 1].story_link || if (index === -1 || index === this.stories.length - 1) {
!this.users[index + 1].submit_time
) {
if (index === -1 || index === this.users.length - 1) {
modal.close(); modal.close();
return; return;
} }
index++; index++;
} }
this.selectedUser = this.users[index + 1]; this.selectedStory = this.stories[index + 1];
} }
updateStandUp(name:string, desc:string) { updateStandUp(name:string, desc:string) {
this.standup.name = name; this.standup.name = name;
this.standup.description = desc; this.standup.description = desc;
this.standupService.updateOne(this.standup).subscribe( this.standupService.updateOne(this.standup).pipe(take(1)).subscribe(
data => { data => {
console.log('Success', data); console.log('Success', data);
}, },
@ -133,7 +128,7 @@ export class HuddleComponent implements OnInit {
} }
deleteStandUp() { deleteStandUp() {
this.standupService.deleteOne(this.standup).subscribe( this.standupService.deleteOne(this.standup).pipe(take(1)).subscribe(
data => { data => {
console.log('Success', data); console.log('Success', data);
}, },
@ -143,12 +138,11 @@ export class HuddleComponent implements OnInit {
); );
} }
async uploadStory(base64: string) { async uploadStory(src: string) {
console.log('uploadStory', event); console.log('uploadStory', event);
const user: SupabaseAuthUser = await this.supaService.client.auth.user(); 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, src);
const story = new Story(user.id, this.standup.id, base64); this.storyService.addOne(story).pipe(take(1)).subscribe(
this.storyService.addOne(story).subscribe(
data => { data => {
console.log('Success', data); console.log('Success', data);
}, },
@ -159,7 +153,28 @@ export class HuddleComponent implements OnInit {
} }
loadStories(id: number) { loadStories(id: number) {
return this.storyService.getStories(id); return this.storyService.getStories(id).pipe(takeUntil(this.unsubscribe.asObservable()));
}
base64ToSafeURL(b64Data: string, contentType: string ='video/webm', sliceSize: number=512): SafeUrl {
// https://stackoverflow.com/questions/16245767/creating-a-blob-from-a-base64-string-in-javascript#16245768
const byteCharacters = atob(b64Data.split('base64,')[1]);
const byteArrays = [];
for (let offset = 0; offset < byteCharacters.length; offset += sliceSize) {
const slice = byteCharacters.slice(offset, offset + sliceSize);
const byteNumbers = new Array(slice.length);
for (let i = 0; i < slice.length; i++) {
byteNumbers[i] = slice.charCodeAt(i);
}
const byteArray = new Uint8Array(byteNumbers);
byteArrays.push(byteArray);
}
const blob = new Blob(byteArrays, {type: contentType});
return this.sanitizer.bypassSecurityTrustUrl(URL.createObjectURL(blob));
} }
} }

View file

@ -39,9 +39,13 @@ export class RecorderComponent implements OnInit {
} }
startRecording() { startRecording() {
if (!this.stream) {
return;
}
this.isRecording = true; this.isRecording = true;
this.recorder = new rrtc.RecordRTCPromisesHandler(this.stream, { this.recorder = new rrtc.RecordRTCPromisesHandler(this.stream, {
type: 'video', type: 'video',
mimeType: 'video/webm;codecs=vp8',
}); });
this.recorder.startRecording(); this.recorder.startRecording();
} }
@ -50,21 +54,11 @@ export class RecorderComponent implements OnInit {
const that = this; const that = this;
this.isRecording = false; this.isRecording = false;
await this.recorder.stopRecording(); await this.recorder.stopRecording();
let blob = await this.recorder.getBlob(); let blob = await this.recorder.getDataURL();
// read as b64 that.recordingEnded.emit(blob);
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(); that.modal.close();
} // rrtc.invokeSaveAsDialog(blob, 'Recorded-Video.webm');
const handleError = (error) => { return;
console.error(error);
}
reader.onerror = handleError;
reader.onabort = handleError;
} }
} }