@@ -2,7 +2,7 @@ import { initializeApp } from "firebase/app";
22import { getAuth , GoogleAuthProvider , onAuthStateChanged } from "firebase/auth" ;
33import { get , getDatabase , ref , set , onValue , push } from "firebase/database" ;
44import { reaction , toJS } from "mobx" ;
5- import throttle from "lodash.throttle" ;
5+ // import throttle from "lodash.throttle";
66
77// Your web app's Firebase configuration
88const firebaseConfig = {
@@ -27,6 +27,24 @@ let noUpload = false;
2727
2828export function connectToFirebase ( model ) {
2929 loadCoursesFromCacheOrFirebase ( model ) ;
30+
31+ // setting missing
32+ // also save filters to local storage
33+ //
34+ const options = JSON . parse ( localStorage . getItem ( "filterOptions" ) ) ;
35+ if ( options ) {
36+ model . setFilterOptions ( options ) ;
37+ console . log ( "Restore options from local storage" ) ;
38+ }
39+
40+ reaction (
41+ ( ) => ( { filterOptions : JSON . stringify ( model . filterOptions ) } ) ,
42+ // eslint-disable-next-line no-unused-vars
43+ ( { filterOptions} ) => {
44+ localStorage . setItem ( "filterOptions" , filterOptions ) ;
45+ }
46+ ) ;
47+
3048 onAuthStateChanged ( auth , ( user ) => {
3149 if ( user ) {
3250 model . setUser ( user ) ; // Set the user ID once authenticated
@@ -41,7 +59,6 @@ export function connectToFirebase(model) {
4159
4260// fetches all relevant information to create the model
4361async function firebaseToModel ( model ) {
44- if ( ! model . user ) return ;
4562 const userRef = ref ( db , `users/${ model . user . uid } ` ) ;
4663 onValue ( userRef , ( snapshot ) => {
4764 if ( ! snapshot . exists ( ) ) return ;
@@ -50,10 +67,9 @@ async function firebaseToModel(model) {
5067 if ( data . favourites ) model . setFavourite ( data . favourites ) ;
5168 if ( data . currentSearchText )
5269 model . setCurrentSearchText ( data . currentSearchText ) ;
53- if ( data . scrollPosition )
54- model . setScrollPosition ( data . scrollPosition ) ;
55- // if (data.currentSearch)
56- // model.setCurrentSearch(data.currentSearch);
70+ // if (data.scrollPosition)
71+ // model.setScrollPosition(data.scrollPosition);
72+ // if (data.filterOptions) model.setFilterOptions(data.filterOptions);
5773 noUpload = false ;
5874 } ) ;
5975}
@@ -64,16 +80,17 @@ export function syncModelToFirebase(model) {
6480 userId : model ?. user . uid ,
6581 favourites : toJS ( model . favourites ) ,
6682 currentSearchText : toJS ( model . currentSearchText ) ,
67- // currentSearch : toJS(model.currentSearch ),
83+ // filterOptions : toJS(model.filterOptions ),
6884 // Add more per-user attributes here
6985 } ) ,
7086 // eslint-disable-next-line no-unused-vars
71- ( { userId, favourites, currentSearchText } ) => {
87+ ( { userId, favourites, currentSearchText, filterOptions } ) => {
7288 if ( noUpload || ! userId ) return ;
7389 const userRef = ref ( db , `users/${ userId } ` ) ;
7490 const dataToSync = {
7591 favourites,
7692 currentSearchText,
93+ // filterOptions,
7794 } ;
7895
7996 set ( userRef , dataToSync )
@@ -86,40 +103,60 @@ export function syncModelToFirebase(model) {
86103export function syncScrollPositionToFirebase ( model , containerRef ) {
87104 if ( ! containerRef ?. current ) return ;
88105 let lastSavedPosition = 0 ;
89-
90- // const throttledSet = throttle((scrollPixel) => {
91- // if (model?.user?.uid) {
92- // const userRef = ref(db, `users/${model.user.uid}/scrollPosition`);
93- // set(userRef, scrollPixel).catch(console.error);
94- // }
95- // }, 500);
96-
97- const handleScroll = ( ) => {
98- const scrollTop = containerRef . current . scrollTop ;
106+
107+ // const throttledSet = throttle((scrollPixel) => {
108+ // if (model?.user?.uid) {
109+ // const userRef = ref(db, `users/${model.user.uid}/scrollPosition`);
110+ // set(userRef, scrollPixel).catch(console.error);
111+ // }
112+ // }, 500);
113+
114+ const handleScroll = ( ) => {
115+ const scrollTop = containerRef . current . scrollTop ;
99116 // make a 100px threshold
100- if ( Math . abs ( scrollTop - lastSavedPosition ) < 100 )
101- return ;
117+ if ( Math . abs ( scrollTop - lastSavedPosition ) < 100 ) return ;
102118
103119 lastSavedPosition = scrollTop ;
104- model . setScrollPosition ( scrollTop ) ;
105- localStorage . setItem ( "scrollPosition" , scrollTop ) ;
106- // throttledSet(scrollTop);
107- } ;
120+ model . setScrollPosition ( scrollTop ) ;
121+ localStorage . setItem ( "scrollPosition" , scrollTop ) ;
122+ // throttledSet(scrollTop);
123+ } ;
108124
109- containerRef . current . addEventListener ( 'scroll' , handleScroll ) ;
110- return ( ) => containerRef . current ?. removeEventListener ( 'scroll' , handleScroll ) ;
125+ containerRef . current . addEventListener ( "scroll" , handleScroll ) ;
126+ return ( ) =>
127+ containerRef . current ?. removeEventListener ( "scroll" , handleScroll ) ;
111128}
112129
130+ function saveCoursesToCache ( courses , timestamp ) {
131+ const request = indexedDB . open ( "CourseDB" , 1 ) ;
113132
114- function saveCoursesInChunks ( courses , timestamp ) {
115- const parts = 3 ; // Adjust this based on course size
116- const chunkSize = Math . ceil ( courses . length / parts ) ;
133+ request . onupgradeneeded = ( event ) => {
134+ const db = event . target . result ;
135+ if ( ! db . objectStoreNames . contains ( "courses" ) ) {
136+ db . createObjectStore ( "courses" , { keyPath : "id" } ) ;
137+ }
138+ if ( ! db . objectStoreNames . contains ( "metadata" ) ) {
139+ db . createObjectStore ( "metadata" , { keyPath : "key" } ) ;
140+ }
141+ } ;
117142
118- for ( let i = 0 ; i < parts ; i ++ ) {
119- const chunk = courses . slice ( i * chunkSize , ( i + 1 ) * chunkSize ) ;
120- localStorage . setItem ( `coursesPart${ i } ` , JSON . stringify ( chunk ) ) ;
121- }
122- localStorage . setItem ( "coursesMetadata" , JSON . stringify ( { parts, timestamp } ) ) ;
143+ request . onsuccess = ( event ) => {
144+ const db = event . target . result ;
145+ const tx = db . transaction ( [ "courses" , "metadata" ] , "readwrite" ) ;
146+ const courseStore = tx . objectStore ( "courses" ) ;
147+ const metaStore = tx . objectStore ( "metadata" ) ;
148+
149+ courseStore . clear ( ) ;
150+ courses . forEach ( ( course ) => courseStore . put ( course ) ) ;
151+ metaStore . put ( { key : "timestamp" , value : timestamp } ) ;
152+
153+ tx . oncomplete = ( ) => console . log ( "Saved courses to IndexedDB" ) ;
154+ tx . onerror = ( e ) => console . error ( "IndexedDB save error" , e ) ;
155+ } ;
156+
157+ request . onerror = ( e ) => {
158+ console . error ( "Failed to open IndexedDB" , e ) ;
159+ } ;
123160}
124161
125162async function updateLastUpdatedTimestamp ( ) {
@@ -155,28 +192,59 @@ export async function fetchAllCourses() {
155192}
156193
157194async function loadCoursesFromCacheOrFirebase ( model ) {
158- // Load metadata from localStorage
159- const cachedMetadata = JSON . parse ( localStorage . getItem ( "coursesMetadata" ) ) ;
160195 const firebaseTimestamp = await fetchLastUpdatedTimestamp ( ) ;
161- // check if up to date
162- if ( cachedMetadata && cachedMetadata . timestamp === firebaseTimestamp ) {
163- console . log ( "Using cached courses..." ) ;
164- let mergedCourses = [ ] ;
165- for ( let i = 0 ; i < cachedMetadata . parts ; i ++ ) {
166- const part = JSON . parse ( localStorage . getItem ( `coursesPart${ i } ` ) ) ;
167- if ( part ) mergedCourses = mergedCourses . concat ( part ) ;
196+ const dbPromise = new Promise ( ( resolve , reject ) => {
197+ const request = indexedDB . open ( "CourseDB" , 1 ) ;
198+ // check if courses and metadata dirs exist
199+ request . onupgradeneeded = ( event ) => {
200+ const db = event . target . result ;
201+ if ( ! db . objectStoreNames . contains ( "courses" ) ) {
202+ db . createObjectStore ( "courses" , { keyPath : "id" } ) ;
203+ }
204+ if ( ! db . objectStoreNames . contains ( "metadata" ) ) {
205+ db . createObjectStore ( "metadata" , { keyPath : "key" } ) ;
206+ }
207+ } ;
208+
209+ request . onsuccess = ( event ) => resolve ( event . target . result ) ;
210+ request . onerror = ( e ) => reject ( e ) ;
211+ } ) ;
212+
213+ try {
214+ const db = await dbPromise ;
215+ const metaTx = db . transaction ( "metadata" , "readonly" ) ;
216+ const metaStore = metaTx . objectStore ( "metadata" ) ;
217+ const metaReq = metaStore . get ( "timestamp" ) ;
218+ const cachedTimestamp = await new Promise ( ( resolve ) => {
219+ metaReq . onsuccess = ( ) => resolve ( metaReq . result ?. value ?? 0 ) ;
220+ metaReq . onerror = ( ) => resolve ( 0 ) ;
221+ } ) ;
222+
223+ if ( cachedTimestamp === firebaseTimestamp ) {
224+ console . log ( "Using cached courses from IndexedDB..." ) ;
225+ const courseTx = db . transaction ( "courses" , "readonly" ) ;
226+ const courseStore = courseTx . objectStore ( "courses" ) ;
227+ const getAllReq = courseStore . getAll ( ) ;
228+ const cachedCourses = await new Promise ( ( resolve ) => {
229+ getAllReq . onsuccess = ( ) => resolve ( getAllReq . result ) ;
230+ getAllReq . onerror = ( ) => resolve ( [ ] ) ;
231+ } ) ;
232+ model . setCourses ( cachedCourses ) ;
233+ return ;
168234 }
169- model . setCourses ( mergedCourses ) ;
170- return ;
235+ } catch ( err ) {
236+ console . warn ( "IndexedDB unavailable, falling back to Firebase:" , err ) ;
171237 }
172238
173- // Fetch if outdated or missing
239+ // fallback: fetch from Firebase
174240 console . log ( "Fetching courses from Firebase..." ) ;
175241 const courses = await fetchAllCourses ( ) ;
176242 model . setCourses ( courses ) ;
177- saveCoursesInChunks ( courses , firebaseTimestamp ) ;
243+ saveCoursesToCache ( courses , firebaseTimestamp ) ;
244+
178245}
179246
247+
180248export async function saveJSONCoursesToFirebase ( model , data ) {
181249 if ( ! data || ! model ) {
182250 console . log ( "no model or data" ) ;
@@ -216,11 +284,11 @@ export async function getReviewsForCourse(courseCode) {
216284 const snapshot = await get ( reviewsRef ) ;
217285 if ( ! snapshot . exists ( ) ) return [ ] ;
218286 const reviews = [ ] ;
219- snapshot . forEach ( childSnapshot => {
287+ snapshot . forEach ( ( childSnapshot ) => {
220288 reviews . push ( {
221289 id : childSnapshot . key ,
222- ...childSnapshot . val ( )
290+ ...childSnapshot . val ( ) ,
223291 } ) ;
224292 } ) ;
225293 return reviews ;
226- }
294+ }
0 commit comments