From 884f82886b2430ae5ddd67b8e252b943569177dd Mon Sep 17 00:00:00 2001 From: "Chris\\Laptop" Date: Thu, 2 May 2019 15:42:43 +0200 Subject: [PATCH] stronger typings for db endpoints, wip howto store methods --- .../Howto/Content/CreateHowto/CreateHowto.tsx | 30 +++++++++------- src/pages/Howto/Content/Howto/Howto.tsx | 16 --------- src/stores/Howto/howto.store.tsx | 35 ++++++++++++------- src/stores/common/module.store.ts | 6 ++-- src/stores/database.tsx | 21 +++++++---- 5 files changed, 59 insertions(+), 49 deletions(-) diff --git a/src/pages/Howto/Content/CreateHowto/CreateHowto.tsx b/src/pages/Howto/Content/CreateHowto/CreateHowto.tsx index 03fb5a4262..f79fb635f7 100644 --- a/src/pages/Howto/Content/CreateHowto/CreateHowto.tsx +++ b/src/pages/Howto/Content/CreateHowto/CreateHowto.tsx @@ -4,7 +4,6 @@ import { Form, Field } from 'react-final-form' import { FieldArray } from 'react-final-form-arrays' import arrayMutators from 'final-form-arrays' import { IHowto, IHowtoFormInput } from 'src/models/howto.models' -import { afs } from 'src/utils/firebase' import TEMPLATE from './TutorialTemplate' import { stripSpecialCharacters } from 'src/utils/helpers' import { UploadedFile } from 'src/pages/common/UploadedFile/UploadedFile' @@ -21,16 +20,20 @@ import { TagsSelectField } from 'src/components/Form/TagsSelect.field' import { ImageInputField } from 'src/components/Form/ImageInput.field' import { FileInputField } from 'src/components/Form/FileInput.field' import posed, { PoseGroup } from 'react-pose' -import { Storage, IUploadedFileMeta } from 'src/stores/storage' -import { IConvertedFileMeta } from 'src/components/ImageInput/ImageInput' +import { inject } from 'mobx-react' +import { Database } from 'src/stores/database' -export interface IState { +interface IState { formValues: IHowtoFormInput formSaved: boolean _docID: string _uploadPath: string _toDocsList: boolean } +interface IProps extends RouteComponentProps {} +interface IInjectedProps extends IProps { + howtoStore: HowtoStore +} const AnimationContainer = posed.div({ enter: { x: 0, opacity: 1, delay: 300 }, @@ -40,17 +43,13 @@ const AnimationContainer = posed.div({ // validation - return undefined if no error (i.e. valid) const required = (value: any) => (value ? undefined : 'Required') -export class CreateHowto extends React.PureComponent< - RouteComponentProps, - IState -> { +@inject('howtoStore') +export class CreateHowto extends React.Component { uploadRefs: { [key: string]: UploadedFile | null } = {} - store = new HowtoStore() constructor(props: any) { super(props) // generate unique id for db and storage references and assign to state - const databaseRef = afs.collection('documentation').doc() - const docID = databaseRef.id + const docID = this.store.generateID() this.state = { formValues: { ...TEMPLATE.TESTING_VALUES, id: docID } as IHowtoFormInput, formSaved: false, @@ -60,9 +59,16 @@ export class CreateHowto extends React.PureComponent< } } + get injected() { + return this.props as IInjectedProps + } + get store() { + return this.injected.howtoStore + } + public onSubmit = async (formValues: IHowtoFormInput) => { console.log('submitting') - // this.injected.store + this.store.uploadHowTo(formValues, this.state._docID) } public validateTitle = async (value: any, meta?: FieldState) => { diff --git a/src/pages/Howto/Content/Howto/Howto.tsx b/src/pages/Howto/Content/Howto/Howto.tsx index 16b89a17c2..fdf379fbae 100644 --- a/src/pages/Howto/Content/Howto/Howto.tsx +++ b/src/pages/Howto/Content/Howto/Howto.tsx @@ -60,22 +60,6 @@ export class Howto extends React.Component< : undefined } - // public renderMultipleImages(step: IHowtoStep) { - // const preloadedImages: any[] = [] - // for (const image of step.images) { - // const imageObj = new Image() - // imageObj.src = image.downloadUrl - // preloadedImages.push({ - // src: imageObj.src, - // }) - // } - // return preloadedImages.map((image: any, index: number) => ( - //
- // - //
- // )) - // } - public render() { const { howto, isLoading } = this.state if (howto) { diff --git a/src/stores/Howto/howto.store.tsx b/src/stores/Howto/howto.store.tsx index 35b6637ea2..27bec5a8a5 100644 --- a/src/stores/Howto/howto.store.tsx +++ b/src/stores/Howto/howto.store.tsx @@ -17,7 +17,7 @@ export class HowtoStore { @action public async getDocList() { const ref = await afs - .collection('documentation') + .collection('howtos') .orderBy('_created', 'desc') .get() @@ -26,7 +26,7 @@ export class HowtoStore { @action public async getDocBySlug(slug: string) { const ref = afs - .collection('documentation') + .collection('howtos') .where('slug', '==', slug) .limit(1) const collection = await ref.get() @@ -40,12 +40,16 @@ export class HowtoStore { public isSlugUnique = async (slug: string) => { try { - await Database.checkSlugUnique('documentation', slug) + await Database.checkSlugUnique('howtos', slug) } catch (e) { return 'How-to titles must be unique, please try being more specific' } } + public generateID = () => { + return Database.generateDocId('howtos') + } + public async uploadHowTo(values: IHowtoFormInput, id: string) { const slug = stripSpecialCharacters(values.tutorial_title) // present uploading modal @@ -69,15 +73,22 @@ export class HowtoStore { private async uploadStepImgs(steps: IHowtoStep[], id: string) { const firstStep = steps[0] const stepImages = firstStep.images as IConvertedFileMeta[] - const promises = stepImages.map(async img => { - const meta = await Storage.uploadFile( - `uploads/howTos/${id}`, - img.name, - img.photoData, - ) - return meta - }) - const imgMeta = await Promise.all(promises) + // NOTE - outer loop could be a map and done in parallel also + for (const step of steps) { + console.log('uploading step', step) + const promises = stepImages.map(async img => { + console.log('uploading image', img) + const meta = await Storage.uploadFile( + `uploads/howTos/${id}`, + img.name, + img.photoData, + ) + return meta + }) + console.log('running all promises') + const imgMeta = await Promise.all(promises) + console.log('imgMeta', imgMeta) + } // const promises = steps.map(step => // step.images.map(async img => { diff --git a/src/stores/common/module.store.ts b/src/stores/common/module.store.ts index b5d6910d77..3be11597d1 100644 --- a/src/stores/common/module.store.ts +++ b/src/stores/common/module.store.ts @@ -1,5 +1,5 @@ import { BehaviorSubject, Subscription } from 'rxjs' -import { Database } from '../database' +import { Database, IDBEndpoints } from '../database' /* The module store contains common methods used across modules that access specfic collections on the database @@ -13,14 +13,14 @@ export class ModuleStore { private activeCollectionSubscription = new Subscription() // when a module store is initiated automatically load the docs in the collection - constructor(public basePath: string) { + constructor(public basePath: IDBEndpoints) { this.getCollection(basePath) } // when accessing a collection want to call the database getCollection method which // efficiently checks the cache first and emits any subsequent updates // we will stop subscribing - public getCollection(path: string) { + public getCollection(path: IDBEndpoints) { this.allDocs$.next([]) this.activeCollectionSubscription.unsubscribe() this.activeCollectionSubscription = Database.getCollection(path).subscribe( diff --git a/src/stores/database.tsx b/src/stores/database.tsx index 6932a2fbdd..af79c18d0f 100644 --- a/src/stores/database.tsx +++ b/src/stores/database.tsx @@ -15,7 +15,7 @@ export class Database { /****************************************************************************** */ // get a group of docs. returns an observable, first pulling from local cache and then searching for updates - public static getCollection(path: string) { + public static getCollection(path: IDBEndpoints) { const collection$ = new Subject() this._emitCollectionUpdates(path, collection$) return collection$ @@ -57,7 +57,7 @@ export class Database { } public static async queryCollection( - collectionPath: string, + collectionPath: IDBEndpoints, field: string, operation: firestore.WhereFilterOp, value: string, @@ -74,14 +74,17 @@ export class Database { /****************************************************************************** */ // instantiate a blank document to generate an id - public static generateId(collectionPath: string) { + public static generateDocId(collectionPath: IDBEndpoints) { return afs.collection(collectionPath).doc().id } public static generateTimestamp(date?: Date) { return firestore.Timestamp.fromDate(date ? date : new Date()) } - public static async checkSlugUnique(collectionPath: string, slug: string) { + public static async checkSlugUnique( + collectionPath: IDBEndpoints, + slug: string, + ) { const matches = await this.queryCollection( collectionPath, 'slug', @@ -95,12 +98,12 @@ export class Database { } } // creates standard set of meta fields applied to all docs - public static generateDocMeta(collectionPath: string, docID?: string) { + public static generateDocMeta(collectionPath: IDBEndpoints, docID?: string) { const user = auth().currentUser const meta: IDbDoc = { _created: this.generateTimestamp(), _deleted: false, - _id: docID ? docID : this.generateId(collectionPath), + _id: docID ? docID : this.generateDocId(collectionPath), _modified: this.generateTimestamp(), _createdBy: user ? (user.displayName as string) : 'anonymous', } @@ -173,3 +176,9 @@ export class Database { return Object.values(json) } } + +/****************************************************************************** * + Interfaces + /****************************************************************************** */ + +export type IDBEndpoints = 'howtos' | 'users' | 'discussions' | 'tags'