Initial commit

This commit is contained in:
MarcUs7i 2025-03-18 22:52:59 +01:00
commit 880a5e58cc
51 changed files with 37278 additions and 0 deletions

34
.github/workflows/manual.yml vendored Normal file
View file

@ -0,0 +1,34 @@
name: Build Manually
on:
workflow_dispatch:
jobs:
build:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- name: Checkout code
uses: actions/checkout@v2
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v1
- name: Login to GitHub Container Registry
uses: docker/login-action@v1
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build and push Docker image
uses: docker/build-push-action@v2
with:
context: .
push: true
platforms: linux/amd64, linux/arm64/v8, linux/arm/v7
tags: ghcr.io/marcus7i/saucekudasai:${{ github.ref_name }},ghcr.io/marcus7i/saucekudasai:latest

35
.github/workflows/release.yml vendored Normal file
View file

@ -0,0 +1,35 @@
name: Build on Release
on:
release:
types: [created]
jobs:
build:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- name: Checkout code
uses: actions/checkout@v2
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v1
- name: Login to GitHub Container Registry
uses: docker/login-action@v1
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build and push Docker image
uses: docker/build-push-action@v2
with:
context: .
push: true
platforms: linux/amd64, linux/arm64/v8, linux/arm/v7
tags: ghcr.io/marcus7i/saucekudasai:${{ github.ref_name }},ghcr.io/marcus7i/saucekudasai:latest

26
.gitignore vendored Normal file
View file

@ -0,0 +1,26 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
/node_modules
/.pnp
.pnp.js
/.vscode
/.eslintcache
# testing
/coverage
# production
/build
# misc
.DS_Store
.env.local
.env.development.local
.env.test.local
.env.production.local
npm-debug.log*
yarn-debug.log*
yarn-error.log*

7
Dockerfile Normal file
View file

@ -0,0 +1,7 @@
FROM node:16-alpine
WORKDIR /app
COPY package*.json .
RUN npm install
COPY . .
EXPOSE 3006
CMD ["npm", "run"]

21
LICENSE Normal file
View file

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2021 Ayush
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

44
README.md Normal file
View file

@ -0,0 +1,44 @@
<!-- @format -->
<h1 align="center">SauceKudasai</h1>
![SauceKudasai](public/SauceKudasai.png)
> A Frontend Search Engine that fetches anime info based on the image provided using <a href="https://github.com/soruly/trace.moe" target="_blank">trace.moe </a> and <a href="https://anilist.gitbook.io/anilist-apiv2-docs/" target="_blank"> AniList</a> API
## Requirements
### Development:
- Node.JS
### Deployment:
- docker
## Development
```sh
npm install
npm run getsauce
```
## Deployment via docker:
Using image:
```sh
docker compose up -d
```
Building image:
```sh
docker compose -f docker-compose-build.yml up -d
```
## Author
Original: **Ayush Gupta**<br>
- Github: https://github.com/ayushgptaa
Modified by: **MarcUs7i**<br>
- GitHub: https://github.com/MarcUs7i
Original repository: https://github.com/ayushgptaa/SauceKudasai

8
docker-compose-build.yml Normal file
View file

@ -0,0 +1,8 @@
services:
app:
build:
context: .
dockerfile: Dockerfile
ports:
- "3006:3006"
command: "npm start"

7
docker-compose.yml Normal file
View file

@ -0,0 +1,7 @@
services:
saucekudasai:
image: ghcr.io/marcus7i/saucekudasai:latest
container_name: saucekudasai
restart: unless-stopped
ports:
- "3006:3006"

5
jsconfig.json Normal file
View file

@ -0,0 +1,5 @@
{
"compilerOptions": {
"baseUrl": "./src"
}
}

35105
package-lock.json generated Normal file

File diff suppressed because it is too large Load diff

56
package.json Normal file
View file

@ -0,0 +1,56 @@
{
"name": "saucekudasai",
"version": "0.1.0",
"private": true,
"description": "A Anime search engine that fetches anime info based on the image provided.",
"keywords": [
"Anime",
"React",
"Anilist",
"Search-Engine",
"Trace-moe",
"anime-search"
],
"repository": {
"type": "git",
"url": "https://github.com/MarcUs7i/SauceKudasai"
},
"dependencies": {
"@testing-library/jest-dom": "^6.6.3",
"@testing-library/react": "^16.2.0",
"@testing-library/user-event": "^14.6.1",
"axios": "^1.8.3",
"framer-motion": "^12.5.0",
"react": "^19.0.0",
"react-dom": "^19.0.0",
"react-dropzone": "^14.3.8",
"react-icons": "^5.5.0",
"react-scripts": "^5.0.1",
"styled-components": "^6.1.16",
"web-vitals": "^4.2.4"
},
"scripts": {
"getsauce": "set PORT=3006 && react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject"
},
"eslintConfig": {
"extends": [
"react-app",
"react-app/jest"
]
},
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
}
}

BIN
public/SauceKudasai.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 904 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 69 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3 KiB

BIN
public/favicon/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 72 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 69 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 118 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 245 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 398 KiB

94
public/index.html Normal file
View file

@ -0,0 +1,94 @@
<!-- @format -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />
<link rel="icon" href="favicon/favicon.ico" />
<link rel="icon" type="image/png" sizes="32x32" href="favicon//favicon-32x32.png" />
<link rel="icon" type="image/png" sizes="16x16" href="favicon//favicon-16x16.png" />
<link rel="icon" type="image/png" sizes="192x19" href="favicon/favicon-192x192.png" />
<link rel="apple-touch-icon" sizes="180x180" type="image/png" href="icons/icon-180x180.png" />
<link rel="apple-touch-icon" sizes="192x192" type="image/png" href="icons/icon-192x192.png" />
<link rel="apple-touch-icon" sizes="256x256" type="image/png" href="icons/icon-1256x256.png" />
<link rel="apple-touch-icon" sizes="382-384" type="image/png" href="icons/icon-382-384.png" />
<link rel="apple-touch-icon" sizes="512x512" type="image/png" href="icons/icon-512x512.png" />
<link rel="manifest" href="manifest.json" />
<meta name="mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-capable" content="yes" />
<meta name="application-name" content="Saucekudasai" />
<meta name="apple-mobile-web-app-title" content="Saucekudasai" />
<meta name="theme-color" content="#E5DFF6" />
<meta name="msapplication-navbutton-color" content="#E5DFF6" />
<meta name="apple-mobile-web-app-status-bar-style" content="#E5DFF6" />
<meta name="msapplication-starturl" content="." />
<!-- Primary Meta Tags -->
<title>SauceKudasai - Search any Anime by Image</title>
<meta name="title" content="SauceKudasai - Search any Anime by Image" />
<meta name="description" content=" Anime search engine that fetches anime info based on the image provided." />
<meta
name="keywords"
content="Saucekudasai, saucekudasai, SauceKudasai.com, sauce, what anime, myanimelist, Anime, free, Search, Tracemoe, Anilist, image, Demon slayer, アニメ, Anime scene search, Image Search, Search Engine"
/>
<!-- Open Graph / Facebook -->
<meta property="og:type" content="website" />
<meta property="og:url" content="https://saucekudasai.com/" />
<meta property="og:title" content="SauceKudasai - Search any Anime by Image" />
<meta
property="og:description"
content="Anime search engine that fetches anime info based on the image provided."
/>
<meta property="og:image" content="https://saucekudasai.com/SauceKudasai.png" />
<!-- Twitter -->
<meta property="twitter:card" content="summary_large_image" />
<meta property="twitter:url" content="https://saucekudasai.com" />
<meta property="twitter:title" content="SauceKudasai - Search any Anime by Image" />
<meta
property="twitter:description"
content="Anime search engine that fetches anime info based on the image provided."
/>
<meta property="twitter:image" content="https://saucekudasai.com/SauceKudasai.png" />
<meta name="twitter:image:alt" content="SauceKudasai - Search any Anime by Image" />
<meta name="twitter:creator" content="@ayushgptaa" />
<link rel="preconnect" href="https://fonts.gstatic.com" />
<link
href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800;900&display=swap"
rel="stylesheet"
/>
<!-- Global site tag (gtag.js) - Google Analytics -->
<script async src="https://www.googletagmanager.com/gtag/js?id=G-98879STR66"></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag() {
dataLayer.push(arguments);
}
gtag('js', new Date());
gtag('config', 'G-98879STR66');
</script>
<!-- Global site tag (gtag.js) - Google Analytics -->
<script async src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=ca-pub-3151427902838816" crossorigin="anonymous"></script>
<style>
#root {
margin: 0;
padding: 0;
box-sizing: border-box;
overflow: hidden;
max-height: 100vh;
background: linear-gradient(116.2deg, #d9e5fa -0.48%, #fad9f3 102.36%);
}
</style>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
</body>
</html>

40
public/manifest.json Normal file
View file

@ -0,0 +1,40 @@
{
"short_name": "Saucekudasai",
"name": "Saucekudasai ",
"lang": "en",
"description": "An anime search engine that provide animeinfo based on image or url provided using trace.moe Api",
"background_color": "#E5DFF6",
"theme_color": "#E5DFF6",
"dir": "auto",
"display": "standalone",
"orientation": "portrait",
"start_url": ".",
"icons": [
{
"src": "icons/icon-180x180.png",
"sizes": "180x180",
"type": "image/png"
},
{
"src": "icons/icon-192x192.png",
"sizes": "192x192",
"type": "image/png"
},
{
"src": "icons/icon-256x256.png",
"sizes": "256x256",
"type": "image/png"
},
{
"src": "icons/icon-384x384.png",
"sizes": "384x384",
"type": "image/png"
},
{
"src": "icons/icon-512x512.png",
"sizes": "512x512",
"type": "image/png"
}
],
"prefer_related_applications": false
}

53
src/Api/constant.js Normal file
View file

@ -0,0 +1,53 @@
/** @format */
import axios from 'axios';
export const TRACE_MOE_QUERY = 'https://api.trace.moe/search';
export const ANILIST_QUERY = 'https://graphql.anilist.co';
// General options for Axios instance
export const options = {
headers: {
' Accept': 'application/json',
"x-trace-key": process.env.API_KEY,
},
};
/**
* Axios instance
*any data provided in instance.get will be merged with options
*/
export const instance = axios.create({
baseURL: TRACE_MOE_QUERY,
options,
});
// Query data for Anilist GraphQL Api
export const query = `
query ($id: Int) {
Media (id:$id , type: ANIME) {
id
title {
english
native
}
description(asHtml:false)
seasonYear
coverImage {
large
}
bannerImage
genres
externalLinks {
id
url
site
}
averageScore
siteUrl
}
}
`;

68
src/App.js Normal file
View file

@ -0,0 +1,68 @@
/** @format */
import { useEffect, useContext } from 'react';
import Fileupload from 'Components/Fileupload/Fileupload';
import Results from 'Components/Resultcard/Results';
import { GlobalStyle } from 'styles/GlobalStyle';
import Navbar from 'Components/Navbar';
import { Footertext } from 'Components/Footer/Footertext';
import { ContextProvider, Context } from 'store/Context-Provider';
import { ServerError, UserError } from 'Components/Ui/Errorcard';
function AppContent() {
const ctx = useContext(Context);
useEffect(() => {
document.body.style.height = window.innerHeight + 'px';
const setheight = () => {
document.body.style.height = window.innerHeight + 'px';
};
window.addEventListener('resize', setheight);
// Clipboard paste event listener
const handlePaste = (e) => {
const items = e.clipboardData?.items;
if (!items) return;
for (let i = 0; i < items.length; i++) {
if (items[i].type.indexOf('image') !== -1) {
const file = items[i].getAsFile();
// Pass as array
ctx.imagehandler([file]);
break;
}
}
};
window.addEventListener('paste', handlePaste);
return () => {
window.removeEventListener('resize', setheight);
window.removeEventListener('paste', handlePaste);
};
}, [ctx]);
return (
<>
<Fileupload />
<Results />
<UserError />
<ServerError />
</>
);
}
function App() {
return (
<div className="App">
<GlobalStyle />
<Navbar />
<ContextProvider>
<AppContent />
</ContextProvider>
<Footertext />
</div>
);
}
export default App;

View file

@ -0,0 +1,85 @@
/** @format */
import { useContext } from 'react';
import Dropzone from 'react-dropzone';
import styled from 'styled-components';
import Previewimage from 'Components/Preview/Previewimage';
import { Imagecontainer, respondTo } from 'styles/mixins';
import Urlinput from 'Components/Ui/Urlinput';
import { Context } from 'store/Context-Provider';
const Dropcontainer = styled.div`
color: #000;
box-sizing: border-box;
height: 75%;
margin: 0.3rem;
background: var(--lavenderlight);
border-radius: 20px;
user-select: none;
padding-top: 1.1rem;
${respondTo.sm`
height: 80%;
`}
${respondTo.md`
height: 75%;
`}
${respondTo.lg`
height: 78%;
border-radius: 25px;
margin: 0.8rem 1.5rem;
`}
`;
const PreviewContainer = styled(Imagecontainer)`
position: relative;
height: 120px;
width: 200px;
margin: auto;
position: relative;
border-radius: calc(var(--radius) / 2);
${respondTo.xs`
height: 150px;
width: 250px;
`}
${respondTo.sm`
height: 160px;
width: 280px;
`}
/* ${respondTo.md`
height: 200px;
width: 330px;
`} */
${respondTo.lg`{
height: 75%;
width: 45%;
`}
`;
const DropZone = ({ showurl, toggleurl }) => {
const ctx = useContext(Context);
const { imagehandler, urlhandler, url } = ctx;
return (
<>
<Dropzone onDrop={imagehandler} accept="image/*" multiple={false} noClick={true}>
{({ getRootProps, getInputProps, open }) => (
<Dropcontainer {...getRootProps()}>
<input {...getInputProps()} />
<PreviewContainer>
<Previewimage open={open} />
</PreviewContainer>
<Urlinput
url={url}
toggleurl={toggleurl}
urlhandler={urlhandler}
showurl={showurl}
open={open}
/>
{/* <Filebtn open={open} toggleurl={toggleurl} showurl={showurl} /> */}
</Dropcontainer>
)}
</Dropzone>
</>
);
};
export default DropZone;

View file

@ -0,0 +1,21 @@
/** @format */
import { useState } from 'react';
import Searchbtn from '../Ui/Searchbtn';
import Dropzone from './DropZone/DropZone';
import { Container } from './Fileuploadstyle';
export const Fileupload = () => {
const [showurl, setShowurl] = useState(false);
const toggleurl = () => {
setShowurl(prevstate => !prevstate);
};
return (
<Container>
<Dropzone showurl={showurl} toggleurl={toggleurl} />
<Searchbtn />
</Container>
);
};
export default Fileupload;

View file

@ -0,0 +1,48 @@
/** @format */
import styled from 'styled-components';
import { respondTo } from '../../styles/mixins';
export const Container = styled.div`
background: var(--primary);
width: 88%;
height: 310px;
padding: 1.3rem;
padding-bottom: 0.5rem;
margin: 1.5em auto;
text-align: center;
background: #ffffff;
box-shadow: 0px 5px 29px -4px rgba(0, 0, 0, 0.25), inset 0px 1px 5px rgba(0, 0, 0, 0.25);
border-radius: 30px;
& p {
font-weight: 600;
font-size: 0.8rem;
margin-top: 1rem;
letter-spacing: 1px;
}
${respondTo.xs`
padding-bottom:0;
height: 320px;
width: 400px;
`}
${respondTo.sm`
height: 350px;
width: 450px;
padding:1.7rem;
padding-bottom:1rem;
`}
${respondTo.md`
height: 350px;
width: 550px;
padding-bottom:0rem;
`}
${respondTo.lg` {
margin-top: 30px;
height: 370px;
width: 750px;
padding:1rem;
`}
`;

View file

@ -0,0 +1,35 @@
/** @format */
import React from 'react';
import styled from 'styled-components';
const Footer = styled.footer`
letter-spacing: 0;
width: 100%;
text-align: center;
position: absolute;
bottom: 0.5rem;
font-size: 0.8rem;
color: #000;
font-weight: var(--semi-bold);
a {
color: var(--link);
}
@media (min-width: 500px) {
font-size: 1.15rem;
font-weight: var(--medium);
}
`;
export const Footertext = () => {
return (
<Footer>
<p>
Powered by &nbsp;
<a href="https://github.com/soruly/trace.moe" target="_blank" rel="noreferrer">
trace.moe
</a>
</p>
</Footer>
);
};

View file

@ -0,0 +1,65 @@
/** @format */
import React from 'react';
import styled from 'styled-components';
import { IconContext } from 'react-icons';
import { BsGithub } from 'react-icons/bs';
import { respondTo } from 'styles/mixins';
const Navbar = styled.nav`
padding: 0.5em 1.5rem;
background: var(--nav);
display: flex;
justify-content: space-between;
align-items: center;
border-radius: 30px;
width: 90%;
margin: auto;
margin-top: 1rem;
mix-blend-mode: normal;
filter: drop-shadow(0px 4px 10px rgba(0, 0, 0, 0.25));
${respondTo.xs`
width: 400px;
`}
${respondTo.sm`
width: 450px;
`}
${respondTo.lg`
margin-top: 1.5rem;
width: 700px;
padding: 0.8rem 2rem;
`}
`;
const Title = styled.h1`
font-size: 1rem;
font-weight: var(--medium);
color: #000;
${respondTo.lg`
font-size: 1.2rem;
`}
`;
const index = () => {
return (
<>
<Navbar>
<Title>SauceKudasai</Title>
<a
href="https://github.com/MarcUs7i/SauceKudasai"
target="_blank"
rel="noreferrer"
aria-label="Github link">
<IconContext.Provider value={{ size: '1.4rem', color: '#000000' }}>
<BsGithub />
</IconContext.Provider>
</a>
</Navbar>
</>
);
};
export default index;

View file

@ -0,0 +1,21 @@
/** @format */
import styled from 'styled-components';
export const Image = styled.img`
border-radius: calc(var(--radius) / 3);
`;
export const Loadingimg = styled(Image)`
filter: blur(2.5px);
`;
export const Video = styled.video`
border-radius: calc(var(--radius) / 2);
`;
export const Mutebtn = styled.button`
background: transparent;
position: absolute;
right: 6px;
bottom: 5px;
`;

View file

@ -0,0 +1,69 @@
/** @format */
import React, { useEffect, useState, useContext } from 'react';
import { Loader } from 'Components/Ui/loader';
import { Image, Loadingimg, Video, Mutebtn } from './PreviewStyles';
import { Uploadinfo } from 'Components/Ui/Uploadinfo/Uploadinfo';
import { Context } from 'store/Context-Provider';
import { IconContext } from 'react-icons';
import { GoUnmute, GoMute } from 'react-icons/go';
const Previewimage = ({ open }) => {
const ctx = useContext(Context);
const { image, url, loading, video } = ctx;
const [preview, setpreview] = useState(null);
const [mute, setmute] = useState(true);
const mutehandler = () => {
setmute(prevstate => !prevstate);
};
// This is use to set preview to the image selected by the user
useEffect(() => {
if (image) return setpreview(URL.createObjectURL(image));
setmute(true);
}, [image]);
// This is use to set preview to the url selected by the user
useEffect(() => {
if (url) setpreview(url);
setmute(true);
}, [url]);
return (
/* This code Checks first if the the video Exits
If the video exits the video tag is set to the video
If the video doesnot exits then it checks if the loading exits
If loading exits then loading is rendered on the screen otherwise
preview is rendered */
<>
{video === null ? (
loading ? (
<>
<Loadingimg src={preview} alt="Loading..." />
<Loader />
</>
) : preview ? (
<Image src={preview} alt="Your Search image" onClick={e => e.stopPropagation()} />
) : (
<Uploadinfo open={open} />
)
) : (
<>
<Video autoPlay loop muted={mute} src={video}></Video>
<Mutebtn onClick={mutehandler}>
{mute ? (
<IconContext.Provider value={{ size: '1.3rem', color: '#d9d9f9' }}>
<GoMute></GoMute>
</IconContext.Provider>
) : (
<IconContext.Provider value={{ size: '1.3rem', color: '#d9d9f9' }}>
<GoUnmute></GoUnmute>
</IconContext.Provider>
)}
</Mutebtn>
</>
)}
</>
);
};
export default Previewimage;

View file

@ -0,0 +1,125 @@
/** @format */
import { useContext } from 'react';
import { Context } from 'store/Context-Provider';
import { AiOutlineInfoCircle } from 'react-icons/ai';
import { IoIosArrowForward } from 'react-icons/io';
import Overlay from '../Ui/Overlay';
import { Closebtn } from 'Components/Ui/Closebtn';
import { AnimatePresence } from 'framer-motion';
import {
Animecard,
Bannerimg,
Banner,
Banneroverlay,
Cover,
Coverimg,
Animeinfo,
Animetext,
Title,
Info,
Details,
Summary,
Links,
Resultfooter,
Moreinfo,
StyledLink,
Similarity,
} from './Resultstyle';
const variants = {
initial: { y: '100%' },
animate: {
y: '0',
transition: {
duration: 0.5,
ease: 'easeInOut',
type: 'linear',
delay: 0.1,
},
},
exit: {
y: '100%',
transition: {
delay: 0.1,
ease: 'easeInOut',
type: 'linear',
duration: 0.8,
},
},
};
const Results = () => {
const ctx = useContext(Context);
const truncate = (str, num) => {
if (str.length <= num) return str;
return str.substring(0, num).concat('...');
};
const closeResult = () => {
ctx.cardhandler();
};
return (
<AnimatePresence>
{ctx.animeinfoexits ? (
<>
<Overlay key="Overlay" onClick={closeResult} />
<Animecard key="animecard" variants={variants} initial="initial" animate="animate" exit="exit">
<Banner>
<Closebtn onClick={closeResult}></Closebtn>
{ctx.animeinfo.bannerImage ? (
<>
<Bannerimg src={ctx.animeinfo.bannerImage}></Bannerimg>
<Banneroverlay></Banneroverlay>{' '}
</>
) : null}
</Banner>
<Animeinfo>
<Cover>
<Coverimg src={ctx.animeinfo.coverImage.large} alt=""></Coverimg>
</Cover>
<Animetext>
<Title>{ctx.animeinfo.title.english || ctx.animeinfo.title.native}</Title>
<Info>
<Details>
<h3>Ep {ctx.animeinfo.episode}</h3>
<h3>at {(ctx.animeinfo.time / 60).toFixed(2).replace('.', ':')}</h3>
<h3>{ctx.animeinfo.seasonYear}</h3>
<Similarity props={ctx.animeinfo.similarity.toFixed(2) * 100}>
{ctx.animeinfo.similarity.toFixed(2) * 100}%
</Similarity>
</Details>
</Info>
<Summary>
<p>{ctx.animeinfo.description ? truncate(ctx.animeinfo.description, 250) : null}</p>
<Links>
{ctx.animeinfo.externalLinks.map(({ id, site, url }) => {
return (
<li key={id}>
<StyledLink href={url} target="_blank">
{site}
</StyledLink>
</li>
);
})}
</Links>
</Summary>
</Animetext>
<Resultfooter>
<Moreinfo href={ctx.animeinfo.siteUrl} target="_blank">
<AiOutlineInfoCircle size={15} />
<span>More Info</span>
<IoIosArrowForward size={15} />
</Moreinfo>
<span>
Information by{' '}
<StyledLink href="https://anilist.com" target="_blank">
Anilist
</StyledLink>
</span>
</Resultfooter>
</Animeinfo>
</Animecard>
</>
) : null}
</AnimatePresence>
);
};
export default Results;

View file

@ -0,0 +1,248 @@
/** @format */
import styled from 'styled-components';
import { motion } from 'framer-motion';
import { respondTo } from 'styles/mixins';
export const Animecard = styled(motion.div)`
z-index: 99;
position: absolute;
bottom: 0;
height: 50%;
width: 100vw;
background: var(--cardbg);
border-radius: var(--card-radius);
`;
export const Banner = styled.div`
background-color: var(--cardbg);
height: 30%;
position: relative;
border-radius: var(--card-radius);
${respondTo.lg`
height: 35%;
`}
`;
export const Bannerimg = styled.img`
border-radius: var(--card-radius);
background-color: var(--cardbg);
`;
export const Banneroverlay = styled.div`
position: absolute;
width: 100%;
height: 100%;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.3);
z-index: 2;
border-radius: var(--card-radius);
`;
export const Cover = styled.div`
width: 120px;
height: 170px;
border-radius: 0.2rem;
z-index: 3;
position: relative;
bottom: 30px;
${respondTo.sm`
width: 140px;
height: 200px;
`}
${respondTo.tab`
width: 160px;
height: 230px;
bottom: 50px;
`}
${respondTo.tab`
width: 160px;
height: 230px;
bottom: 60px;
`}
${respondTo.lg`
width: 180px;
height: 250px;
bottom:85px;
`}
${respondTo.xl`
width: 210px;
height: 270px;
bottom:90px;
`}
`;
export const Coverimg = styled.img`
border-radius: inherit;
`;
export const Animeinfo = styled.div`
padding: 0 0.8rem;
display: grid;
grid-template-columns: auto 1fr;
grid-column-gap: 10px;
${respondTo.tab`
gap: 20px;
`}
`;
export const Animetext = styled.div`
padding-top: 0.4rem;
color: var(--lavender);
`;
export const Title = styled.h1`
font-size: 1.2rem;
font-weight: var(--bold);
${respondTo.md`
font-size: 1.8rem;
`}
${respondTo.lg`
font-size: 2.3rem;
`}
`;
export const Info = styled.div`
display: flex;
align-items: center;
`;
export const Details = styled.div`
margin-top: 0.5rem;
display: flex;
gap: 10px;
font-size: 0.65rem;
${respondTo.tab`
font-size: 0.7rem;
margin-top: 0.2rem;
`}
${respondTo.lg`
font-size: 1rem;
width:60%;
& > h3 {
&:nth-child(2) {
margin-left: 10px;
}
&:nth-child(3) {
margin-left: 10px;
}
&:nth-child(4) {
margin-left: 10px;
}
}
`}
& > h3 {
font-weight: var(--semi-medium);
}
`;
export const Similarity = styled.h3`
font-weight: var(--semi-medium);
color: ${props => (props.props > 90 ? '#15f115' : '#c7e423')};
`;
export const Summary = styled.div`
display: grid;
grid-template-columns: 1fr;
grid-template-rows: auto;
margin-top: 0.7rem;
overflow: hidden;
${respondTo.tab`
grid-gap:15px;
`}
${respondTo.lg`
grid-template-columns: 66% 30%;
grid-gap:45px;
`}
& > p {
display: none;
font-size: 0.8rem;
font-weight: var(--regular);
${respondTo.tab`
display:block;
`}
${respondTo.md`
font-size: 0.9rem;
`}
${respondTo.lg`
font-size: 0.9rem;
`}
${respondTo.xl`
font-size: 1.1rem;
`}
}
`;
export const Genre = styled.div`
display: none;
width: 30%;
${respondTo.xl`
// display:block;
`};
`;
export const Links = styled.div`
font-size: 0.85rem;
overflow: hidden;
${respondTo.lg`
font-size: 0.9rem;
`}
${respondTo.xl`
font-size: 1.1rem;
`}
& > li {
padding-right: 0.6rem;
padding-bottom: 0.3rem;
}
`;
export const Resultfooter = styled.div`
position: absolute;
bottom: 0;
left: 0;
width: 100%;
border-top: 1px solid rgba(255, 255, 255, 0.25);
display: flex;
justify-content: space-between;
align-items: center;
padding: 0.7rem 1rem;
${respondTo.tab`
padding: 0.8rem;
`}
${respondTo.lg`
padding: 0.6rem 1.5rem;
`}
& > span {
font-size: 0.7rem;
}
`;
export const Moreinfo = styled.a`
display: flex;
justify-content: center;
align-items: center;
font-weight: var(--semi-medium);
gap: 4px;
& > span {
font-size: 0.7rem;
${respondTo.lg`
font-size: 1rem;
`}
}
`;
export const StyledLink = styled.a`
color: var(--Styledlinks);
`;
export const Closebtn = styled.div`
display: grid;
place-items: center;
position: absolute;
top: 0.6rem;
right: 0.8rem;
border-radius: 50%;
height: 25px;
width: 25px;
background: rgba(255, 255, 255, 0.7);
cursor: pointer;
z-index: 99;
`;

View file

@ -0,0 +1,26 @@
/** @format */
import React from 'react';
import { IoMdClose } from 'react-icons/io';
import styled from 'styled-components';
const CloseBtn = styled.div`
display: grid;
place-items: center;
position: absolute;
top: 0.6rem;
right: 0.8rem;
border-radius: 50%;
height: 25px;
width: 25px;
background: rgba(255, 255, 255, 0.7);
cursor: pointer;
z-index: 99;
`;
export const Closebtn = ({ onClick }) => {
return (
<CloseBtn onClick={onClick}>
<IoMdClose color={'black'} size={20} />
</CloseBtn>
);
};

View file

@ -0,0 +1,127 @@
/** @format */
import { useContext } from 'react';
import Overlay from '../Ui/Overlay';
import styled from 'styled-components';
import Chikagif from 'Components/Ui/images/chika.gif';
import Kaguyasama from 'Components/Ui/images/kaguya-sama.gif';
import { respondTo } from '../../styles/mixins';
import { Closebtn } from './Closebtn';
import { Context } from 'store/Context-Provider';
import { AnimatePresence, motion } from 'framer-motion';
const Container = styled(motion.div)`
width: 280px;
height: 280px;
z-index: 200;
background: var(--cardbg);
border-radius: 15px;
position: absolute;
top: 45%;
left: 50%;
${respondTo.xs`
height: 320px;
width: 400px;
`}
${respondTo.sm`
height: 400px;
width: 450px;
`}
${respondTo.lg` {
height: 450px;
width: 600px;
`}
`;
const Gif = styled.div`
width: 100%;
border-radius: 15px;
position: relative;
img {
border-radius: 15px 15px 0 0;
user-select: none;
}
`;
const Errormsg = styled.div`
text-align: center;
color: var(--lavender);
padding: 1rem;
h3 {
font-weight: var(--regular);
font-size: 1.3rem;
margin-top: 0.5rem;
${respondTo.sm`
font-size: 1.5rem;
`}
${respondTo.lg`
font-size: 1.6rem;
font-weight: var(--semi-medium);
`}
}
`;
const variants = {
initial: { scale: 0, x: '-50%', y: '-50%' },
animate: {
scale: 1,
transition: {
duration: 0.9,
ease: 'easeInOut',
type: 'spring',
delay: 0.1,
},
},
exit: {
scale: 0,
transition: {
duration: 0.6,
delay: 0.1,
},
},
};
export const Errorcard = ({ image, error, errormsg }) => {
const ctx = useContext(Context);
return (
<AnimatePresence>
{error ? (
<>
<Overlay />
<Container variants={variants} key="Errorcard" initial="initial" animate="animate" exit="exit">
<Gif>
<img src={image} alt="Server Error" />
<Closebtn
onClick={() => {
ctx.errorhandler();
}}
/>
</Gif>
<Errormsg>
<h3>{errormsg}</h3>
</Errormsg>
</Container>
</>
) : null}
</AnimatePresence>
);
};
export const UserError = () => {
const ctx = useContext(Context);
const error = ctx.usererror;
return (
<Errorcard
image={Kaguyasama}
errormsg="Looks like you are offline or using wrong image/url 👉👈"
error={error}
/>
);
};
export const ServerError = () => {
const ctx = useContext(Context);
const error = ctx.servererror;
return <Errorcard image={Chikagif} errormsg="Saba-chan is a little busy at the moment 😓" error={error} />;
};

View file

@ -0,0 +1,51 @@
/** @format */
import React from 'react';
import { FiFolderPlus, FiLink2 } from 'react-icons/fi';
import { IconContext } from 'react-icons';
import styled from 'styled-components';
import { respondTo } from 'styles/mixins';
const Button = styled.button`
position: relative;
border-radius: 50%;
height: 40px;
width: 40px;
border: none;
background: var(--lavender);
cursor: pointer;
margin: 1.4rem 0.5rem 0 0;
cursor: pointer;
& > svg {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
${respondTo.xs`
margin: 0.6rem 0.6rem 0 0;
`}
${respondTo.sm`
margin: 1rem 0.6rem 0 0;
`}
${respondTo.md`
margin: 0.8rem 0.6rem 0 0;
`}
`;
export const Filebtn = ({ open, toggleurl, key1, key2 }) => {
return (
<>
<Button onClick={open} key={key1} aria-label="Fileupload button">
<IconContext.Provider value={{ size: '1.1rem', color: '#303133' }}>
<FiFolderPlus />
</IconContext.Provider>
</Button>
<Button onClick={toggleurl} key={key2} aria-label="Url button">
<IconContext.Provider value={{ size: '1.1rem', color: '#303133' }}>
<FiLink2 />
</IconContext.Provider>
</Button>
</>
);
};

View file

@ -0,0 +1,21 @@
/** @format */
import styled from 'styled-components';
export const Overlay = styled.div`
position: fixed;
width: 100vw;
height: 100vh;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.6);
z-index: 98;
cursor: pointer;
`;
const overlay = ({ onClick }) => {
return <Overlay onClick={onClick} />;
};
export default overlay;

View file

@ -0,0 +1,49 @@
/** @format */
import { useContext, useState, useEffect } from 'react';
import styled from 'styled-components';
import { respondTo } from '../../styles/mixins';
import { Context } from 'store/Context-Provider';
const Search = styled.button`
border: none;
padding: 0.8rem 1.7rem;
background: var(--lavender);
font-family: inherit;
font-weight: var(--semi-medium);
color: #000;
border-radius: calc(var(--radius) * 2);
margin-top: 0.5rem;
letter-spacing: 0;
font-size: 1rem;
filter: drop-shadow(0px 2px 3px rgba(0, 0, 0, 0.25));
cursor: ${props => (props.image || props.url ? 'pointer' : 'not-allowed')};
${respondTo.md`
margin-top: 0.5rem;
padding: 0.9rem 2rem;
`}
${respondTo.lg`
margin-top: 0rem;
font-weight: var(--semi-medium);
font-size: 1.1rem;
`}
`;
const Searchbtn = () => {
const [disable, setdisable] = useState(true);
const ctx = useContext(Context);
const { image, url, fileUpload } = ctx;
useEffect(() => {
if (image || url) return setdisable(false);
}, [image, url]);
return (
<div>
<Search onClick={fileUpload} type="submit" disabled={disable} image={image} url={url}>
Search
</Search>
</div>
);
};
export default Searchbtn;

View file

@ -0,0 +1,65 @@
/** @format */
import Upload from '../images/Ei-share-apple.svg';
import styled from 'styled-components';
import { respondTo } from '../../../styles/mixins';
export const Uploadimg = styled.img`
height: 65px;
width: 40px;
opacity: 75%;
${respondTo.sm`
height: 80px;
width: 60px;
`}
${respondTo.lg`{
margin-top: 0.9rem;
height: 80px;
width: 60px;
opacity: 75%;
`}
`;
export const Uploadtext = styled.div`
margin: 0;
font-size: 1.1rem;
letter-spacing: 0px;
font-weight: var(--semi-medium);
opacity: 80%;
span {
color: var(--lightblue);
cursor: pointer;
}
${respondTo.xs`
font-size: 1.4rem;
`}
${respondTo.md`
font-size: 1.3rem;
`}
${respondTo.lg`
font-weight: var(--semi-medium);
font-size: 1.6rem;
`}
`;
export const PasteHint = styled.div`
margin-top: 0.5rem;
font-size: 0.9rem;
opacity: 70%;
font-style: italic;
`;
export const Uploadinfo = ({ open }) => {
return (
<>
<Uploadimg src={Upload} alt="Upload" />
<Uploadtext>
Drop your images, <span onClick={open}> browse </span> or import from
</Uploadtext>
<PasteHint>Or paste from clipboard (Ctrl+V)</PasteHint>
</>
);
};

View file

@ -0,0 +1,116 @@
/** @format */
import styled from 'styled-components';
import { motion } from 'framer-motion';
import { AnimatePresence } from 'framer-motion';
import { Filebtn } from 'Components/Ui/Filebtn';
import { respondTo } from 'styles/mixins';
import { IoMdClose } from 'react-icons/io';
const UrlContainer = styled.div`
position: relative;
height: 40px;
width: 220px;
display: inline-block;
margin-top: 1.4rem;
${respondTo.xs`
margin: 0.6rem 0.6rem 0 0;
`}
${respondTo.sm`
margin-top: 1rem;
`}
${respondTo.md`
margin-top: 0.8rem ;
font-size: 1rem;
`}
`;
const Url = styled(motion.input)`
position: relative;
border: none;
outline: none;
background: var(--lavender);
padding: 0rem 2.5rem;
height: 40px;
font-family: inherit;
color: #000;
border-radius: calc(var(--radius) * 2);
font-size: 1rem;
&::placeholder {
font-size: 1rem;
font-weight: var(--semi-medium);
color: inherit;
}
`;
const UrlClosebtn = motion.div;
export const CloseBtn = styled.div`
display: grid;
place-items: center;
position: absolute;
top: 50%;
transform: translateY(-50%);
right: 0.8rem;
border-radius: 50%;
height: 24px;
width: 24px;
background: rgba(255, 255, 255, 0.7);
cursor: pointer;
z-index: 99;
`;
const Urlinput = ({ url, toggleurl, urlhandler, showurl, open }) => {
return (
<>
<AnimatePresence exitBeforeEnter={true}>
{showurl ? (
<UrlContainer>
<Url
key="urlbtn"
onClick={e => e.stopPropagation()}
onBlur={toggleurl}
type="url"
name="url"
id="url"
placeholder="Paste your url"
pattern="https://.*"
autoComplete="off"
onChange={urlhandler}
value={url}
initial={{ width: 0 }}
animate={{ width: '220px' }}
exit={{
width: 0,
padding: 0,
transition: {
duration: 1,
ease: 'easeInOut',
},
}}
transition={{
duration: 1,
ease: 'easeInOut',
type: 'spring',
}}
/>
<UrlClosebtn
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{
delay: 1,
}}>
<CloseBtn onClick={toggleurl}>
<IoMdClose color={'black'} size={20} />
</CloseBtn>
</UrlClosebtn>
</UrlContainer>
) : (
<Filebtn open={open} toggleurl={toggleurl} key1="btn1" key2="btn2" />
)}
</AnimatePresence>
</>
);
};
export default Urlinput;

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 50 50" enable-background="new 0 0 50 50"><path d="M30.3 13.7L25 8.4l-5.3 5.3-1.4-1.4L25 5.6l6.7 6.7z"/><path d="M24 7h2v21h-2z"/><path d="M35 40H15c-1.7 0-3-1.3-3-3V19c0-1.7 1.3-3 3-3h7v2h-7c-.6 0-1 .4-1 1v18c0 .6.4 1 1 1h20c.6 0 1-.4 1-1V19c0-.6-.4-1-1-1h-7v-2h7c1.7 0 3 1.3 3 3v18c0 1.7-1.3 3-3 3z"/></svg>

After

Width:  |  Height:  |  Size: 361 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 471 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

View file

@ -0,0 +1,49 @@
/** @format */
import React from 'react';
import styled from 'styled-components';
const Loaderdiv = styled.div`
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
`;
const Svg = styled.svg`
> path,
rect {
fill: var(--lavender);
/* opacity: 0.5; */
}
`;
export const Loader = () => {
return (
<Loaderdiv>
<Svg
version="1.1"
id="loader-1"
xmlns="http://www.w3.org/2000/svg"
x="0px"
y="0px"
width="40px"
height="40px"
viewBox="0 0 50 50">
<path
fill="#000"
d="M43.935,25.145c0-10.318-8.364-18.683-18.683-18.683c-10.318,0-18.683,8.365-18.683,18.683h4.068c0-8.071,6.543-14.615,14.615-14.615c8.072,0,14.615,6.543,14.615,14.615H43.935z">
<animateTransform
attributeType="xml"
attributeName="transform"
type="rotate"
from="0 25 25"
to="360 25 25"
dur="0.6s"
repeatCount="indefinite"
/>
</path>
</Svg>
</Loaderdiv>
);
};

12
src/index.js Normal file
View file

@ -0,0 +1,12 @@
/** @format */
import React from 'react';
import { createRoot } from 'react-dom/client';
import * as serviceworker from './serviceWorkerRegistration';
import App from './App';
const container = document.getElementById('root');
const root = createRoot(container);
root.render(<App />);
serviceworker.register();

36
src/service-worker.js Normal file
View file

@ -0,0 +1,36 @@
/* eslint-disable */
import { clientsClaim } from 'workbox-core';
import { ExpirationPlugin } from 'workbox-expiration';
import { precacheAndRoute } from 'workbox-precaching';
import { registerRoute } from 'workbox-routing';
import { CacheFirst } from 'workbox-strategies';
import * as googleAnalytics from 'workbox-google-analytics';
clientsClaim();
googleAnalytics.initialize();
// Precache all of the assets generated by your build process.
// Their URLs are injected into the manifest variable below.
precacheAndRoute(self.__WB_MANIFEST);
// An example runtime caching route for requests that aren't handled by the
// precache, in this case same-origin .png requests like those from in public/
registerRoute(
// Add in any other file extensions or routing criteria as needed.
({ url }) => url.origin === self.location.origin && url.pathname.endsWith('.png'), // Customize this strategy as needed, e.g., by changing to CacheFirst.
new CacheFirst({
cacheName: 'images',
plugins: [
// Ensure that once this runtime cache reaches a maximum size the
// least-recently used images are removed.
new ExpirationPlugin({ maxEntries: 10 }),
],
})
);
// This allows the web app to trigger skipWaiting via
// registration.waiting.postMessage({type: 'SKIP_WAITING'})
self.addEventListener('message', event => {
if (event.data && event.data.type === 'SKIP_WAITING') {
self.skipWaiting();
}
});

View file

@ -0,0 +1,132 @@
/** @format */
// This optional code is used to register a service worker.
// register() is not called by default.
// This lets the app load faster on subsequent visits in production, and gives
// it offline capabilities. However, it also means that developers (and users)
// will only see deployed updates on subsequent visits to a page, after all the
// existing tabs open on the page have been closed, since previously cached
// resources are updated in the background.
// To learn more about the benefits of this model and instructions on how to
// opt-in, read https://cra.link/PWA
const isLocalhost = Boolean(
window.location.hostname === 'localhost' ||
// [::1] is the IPv6 localhost address.
window.location.hostname === '[::1]' ||
// 127.0.0.0/8 are considered localhost for IPv4.
window.location.hostname.match(/^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/)
);
export function register(config) {
if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {
// The URL constructor is available in all browsers that support SW.
const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href);
if (publicUrl.origin !== window.location.origin) {
// Our service worker won't work if PUBLIC_URL is on a different origin
// from what our page is served on. This might happen if a CDN is used to
// serve assets; see https://github.com/facebook/create-react-app/issues/2374
return;
}
window.addEventListener('load', () => {
const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;
if (isLocalhost) {
// This is running on localhost. Let's check if a service worker still exists or not.
checkValidServiceWorker(swUrl, config);
// Add some additional logging to localhost, pointing developers to the
// service worker/PWA documentation.
navigator.serviceWorker.ready.then(() => {
console.log('This web app is being served cache-first by a service worker.');
});
} else {
// Is not localhost. Just register service worker
registerValidSW(swUrl, config);
}
});
}
}
function registerValidSW(swUrl, config) {
navigator.serviceWorker
.register(swUrl)
.then(registration => {
registration.onupdatefound = () => {
const installingWorker = registration.installing;
if (installingWorker == null) {
return;
}
installingWorker.onstatechange = () => {
if (installingWorker.state === 'installed') {
if (navigator.serviceWorker.controller) {
// At this point, the updated precached content has been fetched,
// but the previous service worker will still serve the older
// content until all client tabs are closed.
console.log(
'New content is available and will be used when all tabs for this page are closed'
);
// Execute callback
if (config && config.onUpdate) {
config.onUpdate(registration);
}
} else {
// At this point, everything has been precached.
// It's the perfect time to display a
// "Content is cached for offline use." message.
console.log('Content is cached for offline use.');
// Execute callback
if (config && config.onSuccess) {
config.onSuccess(registration);
}
}
}
};
};
})
.catch(error => {
console.error('Error during service worker registration:', error);
});
}
function checkValidServiceWorker(swUrl, config) {
// Check if the service worker can be found. If it can't reload the page.
fetch(swUrl, {
headers: { 'Service-Worker': 'script' },
})
.then(response => {
// Ensure service worker exists, and that we really are getting a JS file.
const contentType = response.headers.get('content-type');
if (response.status === 404 || (contentType != null && contentType.indexOf('javascript') === -1)) {
// No service worker found. Probably a different app. Reload the page.
navigator.serviceWorker.ready.then(registration => {
registration.unregister().then(() => {
window.location.reload();
});
});
} else {
// Service worker found. Proceed as normal.
registerValidSW(swUrl, config);
}
})
.catch(() => {
console.log('No internet connection found. App is running in offline mode.');
});
}
export function unregister() {
if ('serviceWorker' in navigator) {
navigator.serviceWorker.ready
.then(registration => {
registration.unregister();
})
.catch(error => {
console.error(error.message);
});
}
}

View file

@ -0,0 +1,153 @@
/** @format */
import { createContext, useState } from 'react';
import { instance, TRACE_MOE_QUERY, ANILIST_QUERY, query } from '../Api/constant';
import React from 'react';
const defaultstate = {
imagehandler: () => {},
image: null,
urlhandler: () => {},
url: null,
fileUpload: () => {},
video: null,
loading: false,
animeinfo: null,
animeinfoexits: false,
cardhandler: () => {},
servererror: false,
usererror: false,
errorhandler: () => {},
};
export const Context = createContext(defaultstate);
export const ContextProvider = props => {
const [image, setimage] = useState(null);
const [url, seturl] = useState('');
const [video, setvideo] = useState(null);
const [loading, setloading] = useState(false);
const [animeinfoexits, setanimeinfoexits] = useState(false);
const [animeinfo, setanimeinfo] = useState({});
const [servererror, setServerError] = useState(false);
const [usererror, setUserError] = useState(false);
// This is used to Change states to default when new image or url is used
const Changestates = () => {
seturl('');
setimage(null);
setloading(false);
setvideo(null);
setanimeinfo({});
setanimeinfoexits(false);
};
// This function is used to Remove the Result card
const cardhandler = () => {
setanimeinfoexits(false);
};
// This function is used to Remove the Error card
const errorhandler = () => {
setServerError(false);
setUserError(false);
};
const fechanimeinfo = async (anilistid, episode, from, similarity) => {
var variables = {
id: anilistid,
};
const body = {
query: query,
variables: variables,
};
try {
const { data } = await instance.post(ANILIST_QUERY, body);
setanimeinfo({
episode: episode,
time: from,
similarity: similarity,
...data.data.Media,
});
setanimeinfoexits(true);
} catch (error) {
console.log(error);
}
};
// Function to get the image when image is selected
const imagehandler = async acceptedfile => {
Changestates();
const file = acceptedfile[0];
if (file && file.type.substr(0, 5) === 'image') return setimage(file);
return setimage(null);
};
// Function to get the url when url is used
const urlhandler = async e => {
Changestates();
const url = e.target.value;
if (url) return seturl(url);
return seturl('');
};
// Get data from the trace.moe API and calls Anilist APi to get animeinfo
const getdata = data => {
const { anilist, video, episode, from, similarity } = data.result[0];
setvideo(video);
setloading(false);
fechanimeinfo(anilist, episode, from, similarity);
};
const fileUpload = async e => {
e.stopPropagation();
setloading(false);
let formData = new FormData();
formData.set('image', image);
const body = formData;
if (image || url) {
setloading(true);
}
try {
if (url) {
const { data } = await instance.get(`?url=${encodeURIComponent(url)}`);
getdata(data);
} else if (image) {
const { data } = await instance.post(TRACE_MOE_QUERY, body);
getdata(data);
}
} catch (error) {
setloading(false);
if (error.response) {
const status = Array.from(String(error.response.status), Number);
if (status[0] === 4) return setUserError(true);
console.log('Something went wrong in the backend', error);
return setServerError(true);
}
if (error.request) {
console.log('Due to network issue or image not provided', error);
return setUserError(true);
}
return console.log('something else happened', error);
}
};
const createContext = {
imagehandler,
image,
urlhandler,
url,
fileUpload,
loading,
video,
animeinfo,
animeinfoexits,
cardhandler,
servererror,
usererror,
errorhandler,
};
return <Context.Provider value={createContext}>{props.children}</Context.Provider>;
};

90
src/styles/GlobalStyle.js Normal file
View file

@ -0,0 +1,90 @@
/** @format */
import { createGlobalStyle } from 'styled-components';
export const GlobalStyle = createGlobalStyle`
:root {
/* Colors */
--lavender: #d9d9f9;
--lavenderlight: #f0f3ff;
--lightblue: #2b2bde;
--link: #0019f6;
--nav: #fff;
--cardbg: #151515;
--Styledlinks: #394ae4;
--border: 1.5px solid #fff;
--radius: 0.9rem;
--card-radius: 15px 15px 0px 0px;
--box-shadow: 0px 5px 29px -4px rgba(0, 0, 0, 0.25), inset 0px 1px 5px rgba(0, 0, 0, 0.25);
/* Font Weights */
--regular: 400;
--semi-medium: 500;
--medium: 600;
--semi-bold: 700;
--bold: 800;
}
html,
body {
position: relative;
box-sizing: border-box;
margin: 0;
padding: 0;
overflow: hidden;
max-height: 100vh;
width: 100%;
color: #fff;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
font-family: 'Inter', sans-serif;
background: linear-gradient(116.2deg, #d9e5fa -0.48%, #fad9f3 102.36%);
text-size-adjust: none;
-webkit-text-size-adjust: none;
-moz-text-size-adjust: none;
-ms-text-size-adjust: none;
}
*,
*:before,
*:after {
padding: 0;
margin: 0;
box-sizing: border-box;
}
img {
height: 100%;
width: 100%;
object-fit: cover;
}
video {
height: 100%;
width: 100%;
object-fit: cover;
}
button {
border: none;
cursor: pointer;
outline: none;
display: inline;
}
div,
p {
outline: none;
}
a {
text-decoration: none;
color: inherit;
}
ul {
list-style: none;
display: inline-block;
}
li {
/* display: inline-block; */
list-style: none;
display: inline-block;
}
`;

30
src/styles/mixins.js Normal file
View file

@ -0,0 +1,30 @@
/** @format */
import styled from 'styled-components';
import { css } from 'styled-components';
export const Imagecontainer = styled.div`
object-fit: cover;
border-radius: var(--radius);
`;
const breakpoints = {
xs: '450px',
sm: '550px',
tab: '660px',
md: '750px',
lg: '900px',
xl: '1100px',
};
export const respondTo = Object.keys(breakpoints).reduce(
(accumulator, label) => {
accumulator[label] = (...args) => css`
@media (min-width: ${breakpoints[label]}) {
${css(...args)};
}
`;
return accumulator;
},
{}
);