import { Observable, forkJoin } from 'rxjs'; import { map } from 'rxjs/operators'; import { PlannedTermEra, PlannedTerm } from '@app/core/models/planned-term'; import { YearMapping } from '@app/core/models/year'; import { YearCode, TermCode } from '@app/core/models/termcode'; export const isValidTermCode = (anything: any): anything is string => { return /^\d{4}$/.test(anything); }; export const pickTermName = (termOffset: string) => { switch (termOffset) { case '2': case '3': return 'fall'; case '4': return 'spring'; case '6': return 'summer'; default: throw new Error(`'${termOffset}' is not a valid term offset`); } }; export const pickTermEra = ( termCode: TermCode, activeTermCodes: ReadonlyArray<TermCode>, ): PlannedTermEra => { const noActiveTermCodes = activeTermCodes.length === 0; const isActiveTermCode = activeTermCodes.some(tc => tc.equals(termCode)); const beforeAllActiveTermCodes = activeTermCodes.every(tc => tc.comesAfter(termCode), ); if (noActiveTermCodes || isActiveTermCode) { return 'active'; } else if (beforeAllActiveTermCodes) { return 'past'; } else { return 'future'; } }; export const pickYearEra = ( yearCode: YearCode, activeTermCodes: ReadonlyArray<TermCode>, ): PlannedTermEra => { const activeYearCodes = activeTermCodes.map(tc => tc.yearCode); const noActiveYearCodes = activeYearCodes.length === 0; const isActiveYearCode = activeYearCodes.some(yc => yc.equals(yearCode)); const beforeAllActiveYearCodes = activeYearCodes.every(yc => yc.comesAfter(yearCode), ); if (noActiveYearCodes || isActiveYearCode) { return 'active'; } else if (beforeAllActiveYearCodes) { return 'past'; } else { return 'future'; } }; export const yearsToDropZoneIds = () => (years$: Observable<YearMapping>) => { return years$.pipe( map(years => { const yearCodes = Object.keys(years); const termCodes = yearCodes.reduce<string[]>((acc, yearCode) => { if (years[yearCode].fall.era !== 'past') { acc = acc.concat(years[yearCode].fall.termCode.toString()); } if (years[yearCode].spring.era !== 'past') { acc = acc.concat(years[yearCode].spring.termCode.toString()); } if (years[yearCode].summer.era !== 'past') { acc = acc.concat(years[yearCode].summer.termCode.toString()); } return acc; }, []); const termIds = [ 'saved-courses', ...termCodes.map(termCode => `term-${termCode}`), ]; return termIds; }), ); }; export const compareArrays = <T>(cmp: (a: T, b: T) => boolean) => { return (a: T[], b: T[]): boolean => { if (a.length === b.length) { return a.every((elem, i) => cmp(elem, b[i])); } else { return false; } }; }; export const compareStringArrays = compareArrays<string>((a, b) => a === b); export const yearsToYearCodes = () => (years$: Observable<YearMapping>) => { return years$.pipe( map(years => { const yearCodes = Object.keys(years); const sortedYearCodes = yearCodes.sort(); return sortedYearCodes; }), ); }; export const yearsToDroppableTermCodes = () => ( years$: Observable<YearMapping>, ) => { return years$.pipe( map(years => { const yearCodes = Object.keys(years); return yearCodes.reduce<string[]>((acc, yearCode) => { if (years[yearCode].fall.era !== 'past') { acc = acc.concat(years[yearCode].fall.termCode.toString()); } if (years[yearCode].spring.era !== 'past') { acc = acc.concat(years[yearCode].spring.termCode.toString()); } if (years[yearCode].summer.era !== 'past') { acc = acc.concat(years[yearCode].summer.termCode.toString()); } return acc; }, []); }), ); }; interface SimpleMap { [name: string]: any; } type ObservableMap<T = SimpleMap> = { [K in keyof T]: Observable<T[K]> }; export const forkJoinWithKeys = <T = SimpleMap>(pairs: ObservableMap<T>) => { const keys = Object.keys(pairs); const observables = keys.map(key => pairs[key]); return forkJoin(observables).pipe( map<any[], T>(values => { const valueMapping = {} as T; keys.forEach((key, index) => { valueMapping[key] = values[index]; }); return valueMapping; }), ); };