[U] huddle.compo upload stories, get stories for correct standup
This commit is contained in:
parent
412a542e03
commit
ccde75f72a
5 changed files with 101 additions and 92 deletions
|
|
@ -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.
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -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>
|
||||||
|
|
@ -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));
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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();
|
that.modal.close();
|
||||||
reader.readAsDataURL(blob);
|
// rrtc.invokeSaveAsDialog(blob, 'Recorded-Video.webm');
|
||||||
reader.onloadend = function() {
|
return;
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
Loading…
Reference in a new issue