handling loading + slicing kuis page
This commit is contained in:
parent
a3a2bd255b
commit
9a0bb1e26e
|
@ -8,6 +8,7 @@
|
||||||
"name": "ksuli-sibi",
|
"name": "ksuli-sibi",
|
||||||
"version": "0.0.0",
|
"version": "0.0.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@fontsource/poppins": "^5.1.1",
|
||||||
"@mediapipe/tasks-vision": "^0.10.14",
|
"@mediapipe/tasks-vision": "^0.10.14",
|
||||||
"@tensorflow/tfjs": "^4.20.0",
|
"@tensorflow/tfjs": "^4.20.0",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
|
@ -842,6 +843,12 @@
|
||||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@fontsource/poppins": {
|
||||||
|
"version": "5.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@fontsource/poppins/-/poppins-5.1.1.tgz",
|
||||||
|
"integrity": "sha512-PQVKHGQOK+UpVNPzTFF9Z9ez3CIDkunfvRXGH95hiDoqWJc1shW4JFqe4tEoqnq7RtZaYrw9aWQq58L4eny5xg==",
|
||||||
|
"license": "OFL-1.1"
|
||||||
|
},
|
||||||
"node_modules/@humanwhocodes/module-importer": {
|
"node_modules/@humanwhocodes/module-importer": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz",
|
||||||
|
|
|
@ -10,6 +10,7 @@
|
||||||
"preview": "vite preview"
|
"preview": "vite preview"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@fontsource/poppins": "^5.1.1",
|
||||||
"@mediapipe/tasks-vision": "^0.10.14",
|
"@mediapipe/tasks-vision": "^0.10.14",
|
||||||
"@tensorflow/tfjs": "^4.20.0",
|
"@tensorflow/tfjs": "^4.20.0",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
|
|
Binary file not shown.
After Width: | Height: | Size: 9.6 KiB |
Binary file not shown.
After Width: | Height: | Size: 3.6 KiB |
|
@ -1,6 +1,7 @@
|
||||||
import { Suspense } from "react";
|
import { Suspense } from "react";
|
||||||
import { Route, Routes } from "react-router-dom";
|
import { Route, Routes } from "react-router-dom";
|
||||||
import myRoute from "./routes/routes";
|
import myRoute from "./routes/routes";
|
||||||
|
import MyLoading from "./components/organisms/MyLoading";
|
||||||
|
|
||||||
const App = () => {
|
const App = () => {
|
||||||
return (
|
return (
|
||||||
|
@ -11,7 +12,7 @@ const App = () => {
|
||||||
key={index}
|
key={index}
|
||||||
path={route.path}
|
path={route.path}
|
||||||
element={
|
element={
|
||||||
<Suspense fallback={<div>Loading...</div>}>
|
<Suspense fallback={<MyLoading />}>
|
||||||
<route.component />
|
<route.component />
|
||||||
</Suspense>
|
</Suspense>
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,4 @@
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import { Link } from "react-router-dom";
|
|
||||||
|
|
||||||
type NavLinkProps = {
|
type NavLinkProps = {
|
||||||
href: string;
|
href: string;
|
||||||
name: string;
|
name: string;
|
||||||
|
@ -9,7 +7,7 @@ type NavLinkProps = {
|
||||||
|
|
||||||
const NavLink = ({ href, name, isActive }: NavLinkProps) => {
|
const NavLink = ({ href, name, isActive }: NavLinkProps) => {
|
||||||
return (
|
return (
|
||||||
<Link to={href}>
|
<a href={href}>
|
||||||
<li
|
<li
|
||||||
className={cn(
|
className={cn(
|
||||||
"btn bg-transparent border-none",
|
"btn bg-transparent border-none",
|
||||||
|
@ -18,7 +16,7 @@ const NavLink = ({ href, name, isActive }: NavLinkProps) => {
|
||||||
>
|
>
|
||||||
{name}
|
{name}
|
||||||
</li>
|
</li>
|
||||||
</Link>
|
</a>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -8,7 +8,7 @@ const HeaderPage = () => {
|
||||||
const navStore = useNavbarStore();
|
const navStore = useNavbarStore();
|
||||||
return (
|
return (
|
||||||
<div className="bg-white drop-shadow w-full h-fit sticky top-0 z-[300]">
|
<div className="bg-white drop-shadow w-full h-fit sticky top-0 z-[300]">
|
||||||
<header className="flex flex-row items-center gap-2 justify-between px-4 py-3 container max-w-[1200px]">
|
<header className="flex flex-row items-center gap-2 justify-between px-4 py-3 container max-w-[960px]">
|
||||||
<div className="flex gap-1 items-center">
|
<div className="flex gap-1 items-center">
|
||||||
<img
|
<img
|
||||||
className="w-10"
|
className="w-10"
|
||||||
|
@ -17,7 +17,7 @@ const HeaderPage = () => {
|
||||||
/>
|
/>
|
||||||
<div className="form-control">
|
<div className="form-control">
|
||||||
<h1 className="font-semibold">K-SULI</h1>
|
<h1 className="font-semibold">K-SULI</h1>
|
||||||
<p className="text-primary">Kedai Susu Tuli</p>
|
<p className="text-primary text-sm">Kedai Susu Tuli</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<ul
|
<ul
|
||||||
|
|
|
@ -0,0 +1,12 @@
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
const MyLoading: React.FC = () => {
|
||||||
|
return (
|
||||||
|
<div className="flex flex-col items-center justify-center h-screen">
|
||||||
|
<div className="animate-spin rounded-full h-12 w-12 border-t-4 border-b-4 border-primary"></div>
|
||||||
|
<p className="mt-4 text-lg text-gray-700">Loading...</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default MyLoading;
|
|
@ -4,9 +4,9 @@ import FooterPage from "../organisms/FooterPage";
|
||||||
|
|
||||||
const LayoutPage = ({ children }: { children: React.ReactNode }) => {
|
const LayoutPage = ({ children }: { children: React.ReactNode }) => {
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col min-h-svh bg-ground">
|
<div className="flex flex-col min-h-svh bg-white font-poppins">
|
||||||
<HeaderPage />
|
<HeaderPage />
|
||||||
<main className="flex flex-col flex-1 container max-w-[1200px]">
|
<main className="flex flex-col flex-1 container max-w-[960px]">
|
||||||
{children}
|
{children}
|
||||||
</main>
|
</main>
|
||||||
<FooterPage />
|
<FooterPage />
|
||||||
|
|
|
@ -42,13 +42,13 @@ class MediapipeHelper {
|
||||||
|
|
||||||
detectHands = async () => {
|
detectHands = async () => {
|
||||||
if (this.videoRef.current === null) {
|
if (this.videoRef.current === null) {
|
||||||
console.error("Video is not initialized.");
|
// console.error("Video is not initialized.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.videoRef && this.videoRef.current.readyState >= 2) {
|
if (this.videoRef && this.videoRef.current.readyState >= 2) {
|
||||||
if (!this.handLandmarker) {
|
if (!this.handLandmarker) {
|
||||||
console.error("HandLandmarker is not initialized.");
|
// console.error("HandLandmarker is not initialized.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const detections = this.handLandmarker.detectForVideo(
|
const detections = this.handLandmarker.detectForVideo(
|
||||||
|
@ -86,7 +86,6 @@ class MediapipeHelper {
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
requestAnimationFrame(this.detectHands);
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
@import "@fontsource/poppins";
|
||||||
@tailwind base;
|
@tailwind base;
|
||||||
@tailwind components;
|
@tailwind components;
|
||||||
@tailwind utilities;
|
@tailwind utilities;
|
||||||
|
@ -6,4 +7,5 @@
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
|
@apply font-poppins;
|
||||||
}
|
}
|
|
@ -4,6 +4,7 @@ import { FaCircleCheck } from "react-icons/fa6";
|
||||||
import useNavbarStore from "@/stores/NavbarStore";
|
import useNavbarStore from "@/stores/NavbarStore";
|
||||||
import MediapipeHelper from "@/helper/MediapipeHelper";
|
import MediapipeHelper from "@/helper/MediapipeHelper";
|
||||||
import DetectionHelper from "@/helper/DetectionHelper";
|
import DetectionHelper from "@/helper/DetectionHelper";
|
||||||
|
import MyLoading from "@/components/organisms/MyLoading";
|
||||||
|
|
||||||
type PredictResult = {
|
type PredictResult = {
|
||||||
abjad: String;
|
abjad: String;
|
||||||
|
@ -23,13 +24,18 @@ const Home = () => {
|
||||||
const [handPresence, setHandPresence] = useState(false);
|
const [handPresence, setHandPresence] = useState(false);
|
||||||
|
|
||||||
const onHandDetected = async () => {
|
const onHandDetected = async () => {
|
||||||
|
if (!mediapipeHelper || !detectionHelper) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
mediapipeHelper.detectHands();
|
||||||
|
|
||||||
const result = mediapipeHelper.getResult();
|
const result = mediapipeHelper.getResult();
|
||||||
if (result.handPresence) {
|
if (result.handPresence) {
|
||||||
// console.log("Hand Detected");
|
// console.log("Hand Detected");
|
||||||
setHandPresence(true);
|
setHandPresence(true);
|
||||||
|
|
||||||
const predict = await detectionHelper.makePrediction(result.finalResult);
|
const predict = await detectionHelper.makePrediction(result.finalResult);
|
||||||
console.log(predict);
|
|
||||||
|
|
||||||
if (predict) {
|
if (predict) {
|
||||||
setResultPredict((prevState) => ({
|
setResultPredict((prevState) => ({
|
||||||
|
@ -37,7 +43,6 @@ const Home = () => {
|
||||||
...predict,
|
...predict,
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
setHandPresence(false);
|
setHandPresence(false);
|
||||||
}
|
}
|
||||||
|
@ -51,25 +56,20 @@ const Home = () => {
|
||||||
video: true,
|
video: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
setLoadCamera(true);
|
||||||
|
|
||||||
if (videoRef.current) {
|
if (videoRef.current) {
|
||||||
videoRef.current.srcObject = stream;
|
videoRef.current.srcObject = stream;
|
||||||
|
mediapipeHelper = new MediapipeHelper(videoRef);
|
||||||
|
detectionHelper = new DetectionHelper();
|
||||||
}
|
}
|
||||||
|
|
||||||
mediapipeHelper = new MediapipeHelper(videoRef);
|
|
||||||
detectionHelper = new DetectionHelper();
|
|
||||||
|
|
||||||
onHandDetected();
|
onHandDetected();
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error accessing webcam:", error);
|
console.error("Error accessing webcam:", error);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
const store = useNavbarStore();
|
const store = useNavbarStore();
|
||||||
let mediapipeHelper: MediapipeHelper;
|
let mediapipeHelper: MediapipeHelper;
|
||||||
let detectionHelper: DetectionHelper;
|
let detectionHelper: DetectionHelper;
|
||||||
|
@ -79,12 +79,14 @@ const Home = () => {
|
||||||
|
|
||||||
startWebcam();
|
startWebcam();
|
||||||
|
|
||||||
setLoadCamera(true);
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
|
if (videoRef.current) {
|
||||||
|
(videoRef.current.srcObject as MediaStream)
|
||||||
|
?.getTracks()
|
||||||
|
.forEach((track) => {
|
||||||
|
track.stop();
|
||||||
|
});
|
||||||
|
}
|
||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
@ -113,7 +115,10 @@ const Home = () => {
|
||||||
></video>
|
></video>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div>Loading...</div>
|
<div className="flex flex-col items-center justify-center flex-1">
|
||||||
|
<div className="animate-spin rounded-full h-12 w-12 border-t-4 border-b-4 border-primary"></div>
|
||||||
|
<p className="mt-4 text-lg text-gray-700">Loading...</p>
|
||||||
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</LayoutPage>
|
</LayoutPage>
|
||||||
|
|
|
@ -11,14 +11,35 @@ const Kuis = () => {
|
||||||
return (
|
return (
|
||||||
<LayoutPage>
|
<LayoutPage>
|
||||||
<div className="flex flex-col flex-1 py-4">
|
<div className="flex flex-col flex-1 py-4">
|
||||||
<h1 className="font-semibold">Kuis SIBI</h1>
|
<h1 className="font-semibold text-3xl">Ayoo Kuiss</h1>
|
||||||
<div className="grid grid-cols-2 md:grid-cols-4 gap-6 mt-6">
|
<p>Be the first!</p>
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-24 md:gap-6 mt-24 md:mt-52 h-full mb-12">
|
||||||
|
<div className="relative ">
|
||||||
|
<img
|
||||||
|
className="absolute w-24 -top-12 left-4 md:left-12"
|
||||||
|
src="/assets/images/tebak-huruf.png"
|
||||||
|
alt="Tebak Huruf"
|
||||||
|
/>
|
||||||
|
<div className="flex flex-col w-full bg-[#7FAFEF] hover:bg-[#6290cc] rounded-md px-4 md:px-12 py-6 pb-12 text-white pt-32 duration-300">
|
||||||
|
<h1 className="font-semibold text-4xl">Tebak Huruf</h1>
|
||||||
|
<p>Tebak huruf dan coba simulasikan</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="relative ">
|
||||||
|
<img
|
||||||
|
className="absolute w-42 -top-12 left-4 md:left-12"
|
||||||
|
src="/assets/images/menyusun-huruf.png"
|
||||||
|
alt="Menyusun Huruf"
|
||||||
|
/>
|
||||||
|
<div className="flex flex-col w-full bg-[#FF6884] hover:bg-[#d3546b] rounded-md px-4 md:px-12 py-6 pb-12 text-white pt-32 duration-300">
|
||||||
|
<h1 className="font-semibold text-4xl">Menyusun Huruf</h1>
|
||||||
|
<p>Susun huruf jadi kata yang tepat</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</LayoutPage>
|
</LayoutPage>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
export default Kuis;
|
export default Kuis;
|
||||||
|
|
|
@ -3,6 +3,9 @@ export default {
|
||||||
content: ["./index.html", "./src/**/*.{js,ts,jsx,tsx}"],
|
content: ["./index.html", "./src/**/*.{js,ts,jsx,tsx}"],
|
||||||
theme: {
|
theme: {
|
||||||
extend: {
|
extend: {
|
||||||
|
fontFamily: {
|
||||||
|
poppins: ["Poppins", "sans-serif"],
|
||||||
|
},
|
||||||
container: {
|
container: {
|
||||||
center: true,
|
center: true,
|
||||||
padding: "1rem",
|
padding: "1rem",
|
||||||
|
|
Loading…
Reference in New Issue