DevAbdoTolba / theday

https://the-day.vercel.app
18 stars 3 forks source link

better fetch method #76

Closed DevAbdoTolba closed 3 months ago

DevAbdoTolba commented 3 months ago

1- first : Fetch the data normally from the drive with a 4529ms delay give the user a notification that is is only once 2- sec : store the fetch in localstorage for the next time 3- third : when revisiting get to the same old localstorage but fetch on the background show a loading arrow showing a tooltip that there is some new data being fetched when fetched show the new files with a popup color with high contrast and a notification says how many new files are fetched

DevAbdoTolba commented 3 months ago

now using indexedDB

import React from "react";
import Dexie, { type EntityTable } from "dexie";

// Types
interface Data {
  id: string;
  mimeType: string;
  name: string;
  parents: string[];
  size: number;
}

interface DataMap {
  [key: string]: Data[];
}

interface IndexedContextProps {
  name: string;
  folders: DataMap;
  children: React.ReactNode;
}

interface subjectType {
  id: number;
  name: string;
  folders: DataMap;
}
// Model

export class SubjectModel extends Dexie {
  subjects!: EntityTable<subjectType, "id">;

  constructor() {
    super("subjects");
    this.version(1).stores({
      material: "++id, &name",
    });
    this.subjects.mapToClass(SubjectModel);
  }
}

export const db = new SubjectModel();

export default function IndexedContext({
  name,
  folders,
  children,
}: IndexedContextProps) {
  console.log({ name, folders });
  return <>{children}</>;
}

This is a context an IndexedContext.tsx in my nextjs app.
The idea is that there is a slow fetch from another component. so that when I fetch a value, then I search for the name indexed in the db. if it is found then I return the value from the db and start in the back to still fetch the values. when the fetch is done I compare the values. if they are the same then nothing is going to be done. else is it was not the same it has 3 conditions : 1-either the coming from the fetch is less than the db. thus just update this value to remove it and the context returned a message that says for example {msg : "3 removed", []}. 2- the data coming from the fetch is new from the fetch. in this case it returns for example : {msg : "2 new items", ["123esad" , "1ewasd"]} in which the array has the id of each of the new updates. 3- there is data removed and and data added. thus is this case return {msg : "1 new item, 5 removed" , ["12edsag"]}. also make sure that the data that I want to compare is not interface DataMap {
  [key: string]: Data[];
} but instead in each key of string I want to compare each interface Data {
  id: string;
  mimeType: string;
  name: string;
  parents: string[];
  size: number;
}
 in it
. I want to make the following operations as values to pass from the context: 
1- get where name
2- add. first check if the value is in the db or not. if yes then return the get value of the name. if not found then just add it. if found then retrieve it and then fetch the data and compare by the logic I mentioned before. so logic is not changed. what happens only to check if the subject name is already in the db. then just set it to the data instade of the fetch. And then fetch normally while every thing is showen to the user. but also make sure to show an MUI <LinearProgress /> in the top normally. and if the data check before the fetch was not found in the db. then just normally every thing goes as the normal orginal code but just add the result of that fetch to the db. please make in mind that the fetch gets the data in this format : 
{"OnlineSection":[{"mimeType":"application/octet-stream","parents":["1hqv45HqskCrDbuCTlnhlc2oW0ge06PvV"],"owners":[{"emailAddress":"backteamcs2110@gmail.com"}],"size":"0","id":"1-1EUUtCIsE-7LDxnOlaU-oh5vLBLs_1U","name":"https%3A%2F%2Fwww.youtube.com%2Fwatch%3Fv%3D6Vb73aCYe3U lab 10 15 min"},{"mimeType":"application/octet-stream","parents":["1hqv45HqskCrDbuCTlnhlc2oW0ge06PvV"],"owners":[{"emailAddress":"backteamcs2110@gmail.com"}],"size":"0","id":"1-_RruXvjE4tUZUzeCRevLx-iqbrGr757","name":"https%3A%2F%2Fwww.youtube.com%2Fwatch%3Fv%3DeTKwiIg8Dpw lab 12"},{"mimeType":"application/octet-stream","parents":["1hqv45HqskCrDbuCTlnhlc2oW0ge06PvV"],"owners":[{"emailAddress":"backteamcs2110@gmail.com"}],"size":"0","id":"1-VGQGUudfo0JDz4OOUYlvb7HKfSIkcsB","name":"https%3A%2F%2Fwww.youtube.com%2Fwatch%3Fv%3DLMZTbk6JKqo lap 7"},{"mimeType":"application/octet-stream","parents":["1hqv45HqskCrDbuCTlnhlc2oW0ge06PvV"],"owners":[{"emailAddress":"backteamcs2110@gmail.com"}],"size":"0","id":"1-Rg928aoQPkiBZ3NRY-QQH6RvakwQoSN","name":"https%3A%2F%2Fwww.youtube.com%2Fwatch%3Fv%3DlkFEE-HIV44 lap 6"},{"mimeType":"application/octet-stream","parents":["1hqv45HqskCrDbuCTlnhlc2oW0ge06PvV"],"owners":[{"emailAddress":"backteamcs2110@gmail.com"}],"size":"0","id":"1-OrE-CD5S-hnYmmteRsVlC07TfXlqGnD","name":"https%3A%2F%2Fwww.youtube.com%2Fwatch%3Fv%3D8ZTnbpS2rcw lap 5"},{"mimeType":"application/octet-stream","parents":["1hqv45HqskCrDbuCTlnhlc2oW0ge06PvV"],"owners":[{"emailAddress":"backteamcs2110@gmail.com"}],"size":"0","id":"1-LWBOcXg0MibsWcrLXmG0G_q8uERxGmD","name":"https%3A%2F%2Fwww.youtube.com%2Fwatch%3Fv%3DhgLD3dQUMvs G_A&B_Lab3"},{"mimeType":"application/octet-stream","parents":["1hqv45HqskCrDbuCTlnhlc2oW0ge06PvV"],"owners":[{"emailAddress":"backteamcs2110@gmail.com"}],"size":"0","id":"1-JGVwU4nBKGw_ATfMm5Are7u9G5SsYqx","name":"https%3A%2F%2Fwww.youtube.com%2Fwatch%3Fv%3Dp82i9IAyMZ8 G_A&B Section 3"},{"mimeType":"application/octet-stream","parents":["1hqv45HqskCrDbuCTlnhlc2oW0ge06PvV"],"owners":[{"emailAddress":"backteamcs2110@gmail.com"}],"size":"0","id":"1-HBoEjnSDVe9GYJ7uS3xGpvTgimaABdN","name":"https%3A%2F%2Fwww.youtube.com%2Fwatch%3Fv%3DJJzm0Iivj-U Lab2_GA&B"},{"mimeType":"application/octet-stream","parents":["1hqv45HqskCrDbuCTlnhlc2oW0ge06PvV"],"owners":[{"emailAddress":"backteamcs2110@gmail.com"}],"size":"0","id":"1-BWIH4peU11ZrJ8HB-CUQE4exef0v73o","name":"https%3A%2F%2Fwww.youtube.com%2Fwatch%3Fv%3DvunD5OeVN5Q Section2_A&B"},{"mimeType":"application/octet-stream","parents":["1hqv45HqskCrDbuCTlnhlc2oW0ge06PvV"],"owners":[{"emailAddress":"backteamcs2110@gmail.com"}],"size":"0","id":"1-84ChOkNGMOFAxd0kU705pMHf2prTQGQ","name":"https%3A%2F%2Fwww.youtube.com%2Fwatch%3Fv%3D4DqxnixWpnI G_A&B Section1"}],"OnlineLecture":[{"mimeType":"application/octet-stream","parents":["1vNZ4Wl5z09KcxM4D30sE_jQM6Z1xkljJ"],"owners":[{"emailAddress":"backteamcs2110@gmail.com"}],"size":"0","id":"1-4iTHAHChxmIQhsU4Ygu_BUWmq0qIBOg","name":"https%3A%2F%2Fwww.youtube.com%2Fwatch%3Fv%3DSnN19TITCO4 Database Online Lec"}],"lectures":[{"mimeType":"application/vnd.ms-powerpoint","parents":["1g8EBEoo3p3X0RMZ-HR_QKIUBkWlTPQB2"],"owners":[{"emailAddress":"backteamcs2110@gmail.com"}],"size":"1342976","id":"19jBbHHGbVAW_Ftj6CNgfoRxZpchivplh","name":"Lect 6.ppt"},{"mimeType":"application/vnd.ms-powerpoint","parents":["1g8EBEoo3p3X0RMZ-HR_QKIUBkWlTPQB2"],"owners":[{"emailAddress":"backteamcs2110@gmail.com"}],"size":"1622528","id":"1dv6ocmjecHybpMdcERB2adKCH_JP0Ep5","name":"Lect 5.ppt"},{"mimeType":"application/vnd.ms-powerpoint","parents":["1g8EBEoo3p3X0RMZ-HR_QKIUBkWlTPQB2"],"owners":[{"emailAddress":"backteamcs2110@gmail.com"}],"size":"1588224","id":"1Z7FFWZWInVN6wT512GIe-yzGNpmX-TM1","name":"Lect 4.ppt"},{"mimeType":"application/vnd.ms-powerpoint","parents":["1g8EBEoo3p3X0RMZ-HR_QKIUBkWlTPQB2"],"owners":[{"emailAddress":"backteamcs2110@gmail.com"}],"size":"1287168","id":"1xzu5cwYof5OdY-gfm7U85uUEhyT97W3w","name":"Lect 3.ppt"},{"mimeType":"application/vnd.ms-powerpoint","parents":["1g8EBEoo3p3X0RMZ-HR_QKIUBkWlTPQB2"],"owners":[{"emailAddress":"backteamcs2110@gmail.com"}],"size":"1543680","id":"1TSIS1SCIWr4OWTGrGwu8ltLwB6kWIfOO","name":"Lect 2.ppt"},{"mimeType":"application/pdf","parents":["1g8EBEoo3p3X0RMZ-HR_QKIUBkWlTPQB2"],"owners":[{"emailAddress":"backteamcs2110@gmail.com"}],"size":"4503353","id":"1zrCmK-RCpr60UhcjHJoo1_TOh_J5GDag","name":"Ramez Elmasri, Shamkant B. Navathe - Fundamentals of Database Systems (2015, Pearson) (1).pdf"},{"mimeType":"application/vnd.ms-powerpoint","parents":["1g8EBEoo3p3X0RMZ-HR_QKIUBkWlTPQB2"],"owners":[{"emailAddress":"backteamcs2110@gmail.com"}],"size":"1435648","id":"1abreeveDRLWnGsHFMMPcTfTYB78Jfgo2","name":"Lect 1.ppt"}]}
3- update. to be used in the add logic function
4- delete

Please add exception logic and handle any more cases. make the code as clean and safe and easy as possible to be done

The main component in which fetches the data and the state I want to chow the data until it shows the new stuff is [subject].tsx : 
import React, { Suspense, lazy, useState, useEffect } from "react";

import Header from "../../components/Header";
import NoData from "../../components/NoData";
import Search from "./Search";

import CssBaseline from "@mui/material/CssBaseline";
import { Typography, Grid, Box } from "@mui/material";
import Head from "next/head";
import { useRouter } from "next/router";

import Offline from "../../components/Offline";
import { offlineContext } from "../_app";
import NavTabs from "./Tabs";
import Drawer from "./AllDrawer";

// import TabsPC from "./TabsPc";
// import TabsPhone from "./TabsPhone";

import Loading from "../../components/Loading";
const TabsPC = lazy(() => import("./TabsPc"));
const TabsPhone = lazy(() => import("./TabsPhone"));

interface Data {
  id: string;
  mimeType: string;
  name: string;
  parents: string[];
  size: number;
}

interface DataMap {
  [key: string]: Data[];
}

function SubjectPage() {
  const [data, setData] = useState<DataMap>();

  const router = useRouter();
  // get parameters from url
  const [subject, setSubject] = useState("");
  const [subjectLoading, setSubjectLoading] = useState(true);
  const [materialLoading, setMaterialLoading] = useState(true);
  const [dots, setDots] = useState(".");

  let showDrawerFromLocalStorage;

  if (typeof window !== "undefined") {
    showDrawerFromLocalStorage = localStorage.getItem("showDrawer");
  }

  const [showDrawer, setShowDrawer] = useState(
    showDrawerFromLocalStorage === "true"
      ? true
      : showDrawerFromLocalStorage === "false"
      ? false
      : true
  );

  const [offline, setOffline] = React.useContext<boolean[]>(offlineContext);

  useEffect(() => {
    if (router.isReady) {
      const { subject } = router.query;
      console.log("it is", subject);

      setSubject(subject as string);

      setSubjectLoading(false);
    }
  }, [router.isReady]);

  useEffect(() => {
    if (subject) {
      fetch(`/api/subjects/${subject}`)
        .then((res) => {
          if (res.ok) {
            return res.json(); // If response is ok, parse JSON data
          } else {
            throw new Error("Network response was not ok"); // Throw an error if response is not ok
          }
        })
        .then((data) => {
          // if data is null set data to {}
          setData(data);

          console.log(data);
          setMaterialLoading(false);
        })
        .catch((error) => {
          console.error(error);
          setMaterialLoading(false);
        });
    }
  }, [subject]);

  useEffect(() => {
    setTimeout(() => {
      if (materialLoading) {
        // add dots until it reaches dots, then deacrese them until they reach 1 and reapet
        if (dots.length < 3) {
          setDots(dots + ".");
        }
        if (dots.length === 3) {
          setDots("");
        }
      }
    }, 333);
  }, [dots]);

  // event listen if shift + arrow left is pressed log hi
  useEffect(() => {
    const handleKeyDown = (e: KeyboardEvent) => {
      if (e.shiftKey && e.key === "ArrowLeft") {
        setShowDrawer((prev) => {
          localStorage.setItem("showDrawer", (!prev).toString());
          return !prev;
        });
      }
    };

    addEventListener("keydown", handleKeyDown);

    return () => {
      removeEventListener("keydown", handleKeyDown);
    };
  }, []);

  // fetch data from api

  // if data is not fetched yet

  // const router = useRouter();
  // const [subjectAbbreviation, setSubjectAbbreviation] = React.useState("s");
  // useEffect(() => {
  //   const subject = router.query.subject;
  //   console.log("useEffect before fun " + subject);
  //   setSubjectAbbreviation(
  //     fetch(`/api/subjects/${subject}`)
  //       .then((res) => res.json())
  //       .then((data) => {
  //         return data;
  //       })
  //   );
  //   console.log("useEffect after fun " + subjectAbbreviation);
  // }, []);
  //   const { subjectID } = useParams();

  // get the subjcet from data that matches the subject param
  //   const subjects = data.semesters.map((semester) =>
  //     semester.subjects.filter((subject) => subject.abbreviation === subjectID)
  //   );
  //   const subject = subjects
  //     .filter((subject) => subject.length > 0)
  //     .map((subject) => subject[0]);

  return (
    <Box
      sx={{
        overflowX: "hidden",
      }}
    >
      <Head>
        <title>
          {subjectLoading ? (
            <Loading />
          ) : (
            (() => {
              // check if there is a localstorage named "first-visited-subject" if not set it to the current date

              if (!localStorage.getItem("first-visited-subject")) {
                localStorage.setItem(
                  "first-visited-subject",
                  subject.toUpperCase()
                );
              }
              // update localstorage "last-visited-subject" to the current subjcet name
              localStorage.setItem(
                "last-visited-subject",
                subject.toUpperCase()
              );
              return subject.toUpperCase();
            })()
          )}
        </title>
        <link rel="icon" href={"../book.png"} />
        <style>
          {`
              *{
                scroll-behavior: smooth;
              }
            `}
        </style>
      </Head>

      {offline && materialLoading ? (
        <Offline />
      ) : (
        <>
          {materialLoading ? (
            // <Typography
            //   variant="h5"
            //   sx={{
            //     position: "absolute",
            //     top: "50%",
            //     left: "50%",
            //     transform: "translate(-50%,-50%)",
            //   }}
            // >
            //   Loading{dots}
            // </Typography>
            <Loading />
          ) : (
            <Suspense fallback={<Loading />}>
              {!data ? (
                <NoData />
              ) : !Object?.keys(data)?.length ? (
                <NoData />
              ) : (
                <>
                  <Drawer
                    subjectLoading={subjectLoading}
                    subject={subject}
                    data={data}
                    materialLoading={materialLoading}
                    showDrawer={showDrawer}
                  />

                  <TabsPC
                    showDrawer={showDrawer}
                    subjectLoading={subjectLoading}
                    data={data}
                  />
                  <TabsPhone data={data} />
                </>
              )}
            </Suspense>
          )}
        </>
      )}
    </Box>
  );
}

export default SubjectPage;

// function to get subject data from subject api, using subject abbreviation

Also please if you cloud can you using MUI use the Linear determinate component <LinearProgress variant="determinate" value={progress} /> in a new logical value. the fetch normally takes 6 sec at maximum but try to measure the progress using the best practices  and give me the code for the new context and the updated [subject].tsx
the context should be set here : 
_app.tsx :
  return (
    <>
      <Head>
        <title>{"TheDay"}</title>
        {/* <meta name="description" content={description} /> */}
        <link rel="icon" href={"/main.png"} />
      </Head>
      <TranscriptContextProvider>
        <offlineContext.Provider value={[offline, setOffline]}>
          <ThemeProvider theme={theme}>
            <CssBaseline />
            <Image
              src={"/icon-512x512.png"}
              alt="icon"
              width={"200"}
              height={"200"}
              style={{
                position: "fixed",
                top: "50%",
                left: "50%",
                transform: "translate(-50%, -50%)",
                zIndex: -100,
                opacity: 0,
              }}
            />
            <Component {...pageProps} />
            <Analytics />
          </ThemeProvider>
        </offlineContext.Provider>
      </TranscriptContextProvider>
    </>
  );
}

What i require is to : 
1- give me the updated IndexedContext.tsx
2- the updated [subject].tsx
3- the edited _app.tsx