mirror of
https://github.com/AykutSarac/jsoncrack.com.git
synced 2025-01-12 19:02:53 +08:00
styling improvements
This commit is contained in:
parent
8225bc2986
commit
4ed3411f86
@ -9,7 +9,7 @@ const withPWA = require("next-pwa")({
|
|||||||
* @type {import('next').NextConfig}
|
* @type {import('next').NextConfig}
|
||||||
*/
|
*/
|
||||||
const nextConfig = {
|
const nextConfig = {
|
||||||
reactStrictMode: true,
|
reactStrictMode: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
module.exports = withPWA(nextConfig);
|
module.exports = withPWA(nextConfig);
|
||||||
|
BIN
public/assets/icon.png
Normal file
BIN
public/assets/icon.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 44 KiB |
@ -150,7 +150,7 @@ export const Sidebar: React.FC = () => {
|
|||||||
const graphCollapsed = useGraph(state => state.graphCollapsed);
|
const graphCollapsed = useGraph(state => state.graphCollapsed);
|
||||||
const direction = useGraph(state => state.direction);
|
const direction = useGraph(state => state.direction);
|
||||||
const foldNodes = useConfig(state => state.foldNodes);
|
const foldNodes = useConfig(state => state.foldNodes);
|
||||||
const hideEditor = useConfig(state => state.hideEditor);
|
const fullscreen = useConfig(state => state.fullscreen);
|
||||||
|
|
||||||
const handleSave = () => {
|
const handleSave = () => {
|
||||||
const a = document.createElement("a");
|
const a = document.createElement("a");
|
||||||
@ -189,7 +189,7 @@ export const Sidebar: React.FC = () => {
|
|||||||
</Link>
|
</Link>
|
||||||
|
|
||||||
<Tooltip className="mobile" title="Edit JSON">
|
<Tooltip className="mobile" title="Edit JSON">
|
||||||
<StyledElement onClick={() => setConfig("hideEditor", !hideEditor)}>
|
<StyledElement onClick={() => setConfig("fullscreen", !fullscreen)}>
|
||||||
<AiOutlineEdit />
|
<AiOutlineEdit />
|
||||||
</StyledElement>
|
</StyledElement>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
|
@ -29,6 +29,7 @@ const StyledSupportButton = styled.a`
|
|||||||
0 4px 8px rgba(0, 0, 0, 0.07), 0 8px 16px rgba(0, 0, 0, 0.07),
|
0 4px 8px rgba(0, 0, 0, 0.07), 0 8px 16px rgba(0, 0, 0, 0.07),
|
||||||
0 16px 32px rgba(0, 0, 0, 0.07), 0 32px 64px rgba(0, 0, 0, 0.07);
|
0 16px 32px rgba(0, 0, 0, 0.07), 0 32px 64px rgba(0, 0, 0, 0.07);
|
||||||
opacity: 0.7;
|
opacity: 0.7;
|
||||||
|
box-sizing: content-box !important;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
width: 180px;
|
width: 180px;
|
||||||
@ -43,8 +44,6 @@ const StyledSupportButton = styled.a`
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
export const SupportButton = () => {
|
export const SupportButton = () => {
|
||||||
if (location.pathname.includes("widget")) return null;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<StyledSupportButton
|
<StyledSupportButton
|
||||||
href="https://github.com/sponsors/AykutSarac"
|
href="https://github.com/sponsors/AykutSarac"
|
||||||
|
@ -22,7 +22,6 @@ const GlobalStyle = createGlobalStyle`
|
|||||||
font-family: 'Mona Sans';
|
font-family: 'Mona Sans';
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
scroll-behavior: smooth;
|
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
|
||||||
background-color: #000000;
|
background-color: #000000;
|
||||||
@ -32,14 +31,15 @@ const GlobalStyle = createGlobalStyle`
|
|||||||
|
|
||||||
@media only screen and (min-width: 768px) {
|
@media only screen and (min-width: 768px) {
|
||||||
background-color: #000000;
|
background-color: #000000;
|
||||||
opacity: 1;
|
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='100%25' height='100%25' viewBox='0 0 800 800'%3E%3Cg fill-opacity='0.22'%3E%3Ccircle fill='%23000000' cx='400' cy='400' r='600'/%3E%3Ccircle fill='%23110718' cx='400' cy='400' r='500'/%3E%3Ccircle fill='%23220e30' cx='400' cy='400' r='400'/%3E%3Ccircle fill='%23331447' cx='400' cy='400' r='300'/%3E%3Ccircle fill='%23441b5f' cx='400' cy='400' r='200'/%3E%3Ccircle fill='%23552277' cx='400' cy='400' r='100'/%3E%3C/g%3E%3C/svg%3E");
|
||||||
background-image: radial-gradient(#414141 0.5px, #000000 0.5px);
|
background-attachment: fixed;
|
||||||
background-size: 15px 15px;
|
background-size: cover;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
* {
|
* {
|
||||||
-webkit-tap-highlight-color: transparent;
|
-webkit-tap-highlight-color: transparent;
|
||||||
|
scroll-behavior: smooth;
|
||||||
}
|
}
|
||||||
|
|
||||||
.hide {
|
.hide {
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
import { useQuery } from "@tanstack/react-query";
|
|
||||||
import toast from "react-hot-toast";
|
import toast from "react-hot-toast";
|
||||||
import {
|
import {
|
||||||
AiOutlineCloudSync,
|
AiOutlineCloudSync,
|
||||||
@ -10,11 +9,12 @@ import {
|
|||||||
AiOutlineUnlock,
|
AiOutlineUnlock,
|
||||||
} from "react-icons/ai";
|
} from "react-icons/ai";
|
||||||
import { VscAccount } from "react-icons/vsc";
|
import { VscAccount } from "react-icons/vsc";
|
||||||
import { altogic } from "src/api/altogic";
|
import { useJson } from "src/hooks/useFetchedJson";
|
||||||
import { getJson, saveJson, updateJson } from "src/services/db/json";
|
import { saveJson, updateJson } from "src/services/db/json";
|
||||||
import useConfig from "src/store/useConfig";
|
import useConfig from "src/store/useConfig";
|
||||||
import useGraph from "src/store/useGraph";
|
import useGraph from "src/store/useGraph";
|
||||||
import useModal from "src/store/useModal";
|
import useModal from "src/store/useModal";
|
||||||
|
import useStored from "src/store/useStored";
|
||||||
import useUser from "src/store/useUser";
|
import useUser from "src/store/useUser";
|
||||||
import styled from "styled-components";
|
import styled from "styled-components";
|
||||||
|
|
||||||
@ -61,29 +61,30 @@ const StyledBottomBarItem = styled.button`
|
|||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export const BottomBar = () => {
|
const StyledImg = styled.img<{ light: boolean }>`
|
||||||
const { isReady, replace, query } = useRouter();
|
filter: ${({ light }) => light && "invert(100%)"};
|
||||||
|
`;
|
||||||
|
|
||||||
const { data } = useQuery(
|
export const BottomBar = () => {
|
||||||
["dbJson", query.json],
|
const { replace, query } = useRouter();
|
||||||
() => getJson(query.json as string),
|
const { data } = useJson();
|
||||||
{
|
|
||||||
enabled: isReady && !!query.json,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
const user = useUser(state => state.user);
|
const user = useUser(state => state.user);
|
||||||
const setVisible = useModal(state => state.setVisible);
|
const setVisible = useModal(state => state.setVisible);
|
||||||
const getJsonState = useGraph(state => state.getJson);
|
const getJsonState = useGraph(state => state.getJson);
|
||||||
const hasChanges = useConfig(state => state.hasChanges);
|
const hasChanges = useConfig(state => state.hasChanges);
|
||||||
const setConfig = useConfig(state => state.setConfig);
|
const setConfig = useConfig(state => state.setConfig);
|
||||||
|
const lightmode = useStored(state => state.lightmode);
|
||||||
const [isPrivate, setIsPrivate] = React.useState(true);
|
const [isPrivate, setIsPrivate] = React.useState(true);
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
if (data) setIsPrivate(data.data.private);
|
console.log(data);
|
||||||
|
|
||||||
|
setIsPrivate(data?.data.private ?? true);
|
||||||
}, [data]);
|
}, [data]);
|
||||||
|
|
||||||
const handleSaveJson = React.useCallback(() => {
|
const handleSaveJson = React.useCallback(() => {
|
||||||
|
if (!user) return setVisible("login")(true);
|
||||||
if (hasChanges) {
|
if (hasChanges) {
|
||||||
toast.promise(
|
toast.promise(
|
||||||
saveJson({ id: query.json, data: getJsonState() }).then(res => {
|
saveJson({ id: query.json, data: getJsonState() }).then(res => {
|
||||||
@ -97,17 +98,16 @@ export const BottomBar = () => {
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}, [getJsonState, hasChanges, query.json, replace, setConfig]);
|
}, [getJsonState, hasChanges, query.json, replace, setConfig, setVisible, user]);
|
||||||
|
|
||||||
const handleLoginClick = () => {
|
const handleLoginClick = () => {
|
||||||
if (user) return setVisible("account")(true);
|
if (user) return setVisible("account")(true);
|
||||||
altogic.auth.signInWithProvider("google");
|
else setVisible("login")(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
const setPrivate = () => {
|
const setPrivate = () => {
|
||||||
if (!query.json) return handleSaveJson();
|
if (!query.json) return handleSaveJson();
|
||||||
updateJson(query.json as string, { private: !isPrivate });
|
updateJson(query.json as string, { private: !isPrivate });
|
||||||
setIsPrivate(!isPrivate);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -121,26 +121,35 @@ export const BottomBar = () => {
|
|||||||
{hasChanges ? <AiOutlineCloudUpload /> : <AiOutlineCloudSync />}
|
{hasChanges ? <AiOutlineCloudUpload /> : <AiOutlineCloudSync />}
|
||||||
{hasChanges ? "Unsaved Changes" : "Saved"}
|
{hasChanges ? "Unsaved Changes" : "Saved"}
|
||||||
</StyledBottomBarItem>
|
</StyledBottomBarItem>
|
||||||
|
{query.json && (
|
||||||
|
<>
|
||||||
<StyledBottomBarItem onClick={setPrivate}>
|
<StyledBottomBarItem onClick={setPrivate}>
|
||||||
{isPrivate ? <AiOutlineLock /> : <AiOutlineUnlock />}
|
{isPrivate ? <AiOutlineLock /> : <AiOutlineUnlock />}
|
||||||
{isPrivate ? "Private" : "Public"}
|
{isPrivate ? "Private" : "Public"}
|
||||||
</StyledBottomBarItem>
|
</StyledBottomBarItem>
|
||||||
{query.json && (
|
|
||||||
<StyledBottomBarItem onClick={() => setVisible("share")(true)}>
|
<StyledBottomBarItem onClick={() => setVisible("share")(true)}>
|
||||||
<AiOutlineLink />
|
<AiOutlineLink />
|
||||||
Share
|
Share
|
||||||
</StyledBottomBarItem>
|
</StyledBottomBarItem>
|
||||||
|
</>
|
||||||
)}
|
)}
|
||||||
</StyledLeft>
|
</StyledLeft>
|
||||||
<StyledRight>
|
<StyledRight>
|
||||||
|
<a
|
||||||
|
href="https://www.altogic.com/?utm_source=jsoncrack&utm_medium=referral&utm_campaign=sponsorship"
|
||||||
|
rel="sponsored noreferrer"
|
||||||
|
target="_blank"
|
||||||
|
>
|
||||||
<StyledBottomBarItem>
|
<StyledBottomBarItem>
|
||||||
Powered by
|
Powered by
|
||||||
<img
|
<StyledImg
|
||||||
height="20"
|
height="20"
|
||||||
src="https://regexlearn.com/altogic.svg"
|
src="https://regexlearn.com/altogic.svg"
|
||||||
alt="powered by buildable"
|
alt="powered by buildable"
|
||||||
|
light={lightmode}
|
||||||
/>
|
/>
|
||||||
</StyledBottomBarItem>
|
</StyledBottomBarItem>
|
||||||
|
</a>
|
||||||
</StyledRight>
|
</StyledRight>
|
||||||
</StyledBottomBar>
|
</StyledBottomBar>
|
||||||
);
|
);
|
||||||
|
@ -17,25 +17,25 @@ const LiveEditor = dynamic(() => import("src/containers/Editor/LiveEditor"), {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const Panes: React.FC = () => {
|
const Panes: React.FC = () => {
|
||||||
const hideEditor = useConfig(state => state.hideEditor);
|
const fullscreen = useConfig(state => state.fullscreen);
|
||||||
const setConfig = useConfig(state => state.setConfig);
|
const setConfig = useConfig(state => state.setConfig);
|
||||||
const isMobile = window.innerWidth <= 768;
|
const isMobile = window.innerWidth <= 768;
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
if (isMobile) setConfig("hideEditor", true);
|
if (isMobile) setConfig("fullscreen", true);
|
||||||
}, [isMobile, setConfig]);
|
}, [isMobile, setConfig]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<StyledEditor proportionalLayout={false} vertical={isMobile}>
|
<StyledEditor proportionalLayout={false} vertical={isMobile}>
|
||||||
<Allotment.Pane
|
<Allotment.Pane
|
||||||
preferredSize={isMobile ? "100%" : 400}
|
preferredSize={isMobile ? "100%" : 400}
|
||||||
minSize={hideEditor ? 0 : 300}
|
minSize={fullscreen ? 0 : 300}
|
||||||
maxSize={isMobile ? Infinity : 800}
|
maxSize={isMobile ? Infinity : 800}
|
||||||
visible={!hideEditor}
|
visible={!fullscreen}
|
||||||
>
|
>
|
||||||
<JsonEditor />
|
<JsonEditor />
|
||||||
</Allotment.Pane>
|
</Allotment.Pane>
|
||||||
<Allotment.Pane minSize={0} maxSize={isMobile && !hideEditor ? 0 : Infinity}>
|
<Allotment.Pane minSize={0} maxSize={isMobile && !fullscreen ? 0 : Infinity}>
|
||||||
<LiveEditor />
|
<LiveEditor />
|
||||||
</Allotment.Pane>
|
</Allotment.Pane>
|
||||||
</StyledEditor>
|
</StyledEditor>
|
||||||
|
@ -48,13 +48,13 @@ const StyledToolElement = styled.button`
|
|||||||
export const Tools: React.FC = () => {
|
export const Tools: React.FC = () => {
|
||||||
const setVisible = useModal(state => state.setVisible);
|
const setVisible = useModal(state => state.setVisible);
|
||||||
|
|
||||||
const hideEditor = useConfig(state => state.hideEditor);
|
const fullscreen = useConfig(state => state.fullscreen);
|
||||||
const setConfig = useConfig(state => state.setConfig);
|
const setConfig = useConfig(state => state.setConfig);
|
||||||
|
|
||||||
const zoomIn = useConfig(state => state.zoomIn);
|
const zoomIn = useConfig(state => state.zoomIn);
|
||||||
const zoomOut = useConfig(state => state.zoomOut);
|
const zoomOut = useConfig(state => state.zoomOut);
|
||||||
const centerView = useConfig(state => state.centerView);
|
const centerView = useConfig(state => state.centerView);
|
||||||
const toggleEditor = () => setConfig("hideEditor", !hideEditor);
|
const toggleEditor = () => setConfig("fullscreen", !fullscreen);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
@ -2,6 +2,7 @@ import React from "react";
|
|||||||
import Head from "next/head";
|
import Head from "next/head";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import Script from "next/script";
|
import Script from "next/script";
|
||||||
|
import { AiOutlineRight } from "react-icons/ai";
|
||||||
import { FaGithub, FaHeart, FaLinkedin, FaTwitter } from "react-icons/fa";
|
import { FaGithub, FaHeart, FaLinkedin, FaTwitter } from "react-icons/fa";
|
||||||
import {
|
import {
|
||||||
HiCursorClick,
|
HiCursorClick,
|
||||||
@ -51,10 +52,10 @@ const HeroSection = () => {
|
|||||||
<Styles.StyledHighlightedText>instantly</Styles.StyledHighlightedText> into
|
<Styles.StyledHighlightedText>instantly</Styles.StyledHighlightedText> into
|
||||||
graphs.
|
graphs.
|
||||||
</Styles.StyledSubTitle>
|
</Styles.StyledSubTitle>
|
||||||
<Styles.StyledMinorTitle>Paste - Import - Fetch!</Styles.StyledMinorTitle>
|
|
||||||
|
|
||||||
<Styles.StyledButton rel="prefetch" href="/editor" link>
|
<Styles.StyledButton rel="prefetch" href="/editor" link>
|
||||||
GO TO EDITOR
|
GO TO EDITOR
|
||||||
|
<AiOutlineRight strokeWidth="30px" />
|
||||||
</Styles.StyledButton>
|
</Styles.StyledButton>
|
||||||
|
|
||||||
<Styles.StyledButtonWrapper>
|
<Styles.StyledButtonWrapper>
|
||||||
@ -238,8 +239,8 @@ const EmbedSection = () => (
|
|||||||
json: defaultJson,
|
json: defaultJson,
|
||||||
options: {
|
options: {
|
||||||
theme: "dark",
|
theme: "dark",
|
||||||
direction: "DOWN"
|
direction: "DOWN",
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
"*"
|
"*"
|
||||||
);
|
);
|
||||||
@ -288,7 +289,8 @@ const SponsorSection = () => (
|
|||||||
const Footer = () => (
|
const Footer = () => (
|
||||||
<Styles.StyledFooter>
|
<Styles.StyledFooter>
|
||||||
<Styles.StyledFooterText>
|
<Styles.StyledFooterText>
|
||||||
© 2022 JSON Crack - {pkg.version}
|
© <img width="100" src="assets/icon.png" alt="icon" />
|
||||||
|
{new Date().getFullYear()} - {pkg.version}
|
||||||
</Styles.StyledFooterText>
|
</Styles.StyledFooterText>
|
||||||
<Styles.StyledIconLinks>
|
<Styles.StyledIconLinks>
|
||||||
<Styles.StyledNavLink
|
<Styles.StyledNavLink
|
||||||
@ -335,8 +337,8 @@ const Home: React.FC = () => {
|
|||||||
<EmbedSection />
|
<EmbedSection />
|
||||||
<SupportSection />
|
<SupportSection />
|
||||||
<SponsorSection />
|
<SponsorSection />
|
||||||
<Footer />
|
|
||||||
<SupportButton />
|
<SupportButton />
|
||||||
|
<Footer />
|
||||||
</Styles.StyledHome>
|
</Styles.StyledHome>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -137,6 +137,7 @@ export const StyledSubTitle = styled.h2`
|
|||||||
font-size: 2.5rem;
|
font-size: 2.5rem;
|
||||||
max-width: 40rem;
|
max-width: 40rem;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
|
margin-bottom: 25px;
|
||||||
|
|
||||||
@media only screen and (max-width: 768px) {
|
@media only screen and (max-width: 768px) {
|
||||||
font-size: 1.5rem;
|
font-size: 1.5rem;
|
||||||
@ -158,11 +159,12 @@ export const StyledMinorTitle = styled.p`
|
|||||||
export const StyledButton = styled(Button)`
|
export const StyledButton = styled(Button)`
|
||||||
background: ${({ status }) => !status && "#a13cc2"};
|
background: ${({ status }) => !status && "#a13cc2"};
|
||||||
padding: 12px 24px;
|
padding: 12px 24px;
|
||||||
|
height: 46px;
|
||||||
|
|
||||||
div {
|
div {
|
||||||
font-family: "Roboto", sans-serif;
|
font-family: "Mona Sans";
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
font-size: 16px;
|
font-size: 1rem;
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
@ -196,11 +198,15 @@ export const StyledSponsorButton = styled(Button)<{ isBlue?: boolean }>`
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
export const StyledFeaturesSection = styled.section`
|
export const StyledFeaturesSection = styled.section`
|
||||||
display: flex;
|
max-width: 60%;
|
||||||
max-width: 85%;
|
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
gap: 2rem;
|
padding: 0 3%;
|
||||||
padding: 50px 3%;
|
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(2, 1fr);
|
||||||
|
grid-template-rows: repeat(2, 1fr);
|
||||||
|
grid-column-gap: 60px;
|
||||||
|
grid-row-gap: 60px;
|
||||||
|
|
||||||
@media only screen and (max-width: 768px) {
|
@media only screen and (max-width: 768px) {
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
@ -211,6 +217,16 @@ export const StyledFeaturesSection = styled.section`
|
|||||||
export const StyledSectionCard = styled.div`
|
export const StyledSectionCard = styled.div`
|
||||||
text-align: center;
|
text-align: center;
|
||||||
flex: 1;
|
flex: 1;
|
||||||
|
border: 1px solid ${({ theme }) => theme.BACKGROUND_MODIFIER_ACCENT};
|
||||||
|
background: rgb(48, 0, 65);
|
||||||
|
background: linear-gradient(
|
||||||
|
138deg,
|
||||||
|
rgba(48, 0, 65, 0.8870141806722689) 0%,
|
||||||
|
rgba(72, 12, 84, 0.40802258403361347) 33%,
|
||||||
|
rgba(65, 8, 92, 0.6012998949579832) 100%
|
||||||
|
);
|
||||||
|
border-radius: 6px;
|
||||||
|
padding: 16px;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export const StyledCardTitle = styled.div`
|
export const StyledCardTitle = styled.div`
|
||||||
@ -350,6 +366,9 @@ export const StyledFooter = styled.footer`
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
export const StyledFooterText = styled.p`
|
export const StyledFooterText = styled.p`
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 5px;
|
||||||
color: #b4b4b4;
|
color: #b4b4b4;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
@ -4,23 +4,48 @@ import { ClearModal } from "src/containers/Modals/ClearModal";
|
|||||||
import { CloudModal } from "src/containers/Modals/CloudModal";
|
import { CloudModal } from "src/containers/Modals/CloudModal";
|
||||||
import { DownloadModal } from "src/containers/Modals/DownloadModal";
|
import { DownloadModal } from "src/containers/Modals/DownloadModal";
|
||||||
import { ImportModal } from "src/containers/Modals/ImportModal";
|
import { ImportModal } from "src/containers/Modals/ImportModal";
|
||||||
|
import { LoginModal } from "src/containers/Modals/LoginModal";
|
||||||
import { SettingsModal } from "src/containers/Modals/SettingsModal";
|
import { SettingsModal } from "src/containers/Modals/SettingsModal";
|
||||||
import { ShareModal } from "src/containers/Modals/ShareModal";
|
import { ShareModal } from "src/containers/Modals/ShareModal";
|
||||||
import useModal from "src/store/useModal";
|
import useModal from "src/store/useModal";
|
||||||
|
import shallow from "zustand/shallow";
|
||||||
|
|
||||||
export const ModalController = () => {
|
export const ModalController = () => {
|
||||||
const setVisible = useModal(state => state.setVisible);
|
const setVisible = useModal(state => state.setVisible);
|
||||||
const state = useModal(state => state);
|
|
||||||
|
const [
|
||||||
|
importModal,
|
||||||
|
clearModal,
|
||||||
|
downloadModal,
|
||||||
|
settingsModal,
|
||||||
|
cloudModal,
|
||||||
|
accountModal,
|
||||||
|
loginModal,
|
||||||
|
shareModal,
|
||||||
|
] = useModal(
|
||||||
|
state => [
|
||||||
|
state.import,
|
||||||
|
state.clear,
|
||||||
|
state.download,
|
||||||
|
state.settings,
|
||||||
|
state.cloud,
|
||||||
|
state.account,
|
||||||
|
state.login,
|
||||||
|
state.share,
|
||||||
|
],
|
||||||
|
shallow
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<ImportModal visible={state.import} setVisible={setVisible("import")} />
|
<ImportModal visible={importModal} setVisible={setVisible("import")} />
|
||||||
<ClearModal visible={state.clear} setVisible={setVisible("clear")} />
|
<ClearModal visible={clearModal} setVisible={setVisible("clear")} />
|
||||||
<DownloadModal visible={state.download} setVisible={setVisible("download")} />
|
<DownloadModal visible={downloadModal} setVisible={setVisible("download")} />
|
||||||
<SettingsModal visible={state.settings} setVisible={setVisible("settings")} />
|
<SettingsModal visible={settingsModal} setVisible={setVisible("settings")} />
|
||||||
<CloudModal visible={state.cloud} setVisible={setVisible("cloud")} />
|
<CloudModal visible={cloudModal} setVisible={setVisible("cloud")} />
|
||||||
<AccountModal visible={state.account} setVisible={setVisible("account")} />
|
<AccountModal visible={accountModal} setVisible={setVisible("account")} />
|
||||||
<ShareModal visible={state.share} setVisible={setVisible("share")} />
|
<LoginModal visible={loginModal} setVisible={setVisible("login")} />
|
||||||
|
<ShareModal visible={shareModal} setVisible={setVisible("share")} />
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -26,18 +26,6 @@ const StyledTitle = styled.p`
|
|||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const StyledModalContent = styled.div`
|
|
||||||
margin-bottom: 20px;
|
|
||||||
`;
|
|
||||||
|
|
||||||
const StyledLoginWrapper = styled.div`
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
`;
|
|
||||||
|
|
||||||
const StyledButton = styled(Button)``;
|
|
||||||
|
|
||||||
const StyledAccountWrapper = styled.div`
|
const StyledAccountWrapper = styled.div`
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
|
@ -1,12 +1,13 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
|
import { useRouter } from "next/router";
|
||||||
import { useQuery } from "@tanstack/react-query";
|
import { useQuery } from "@tanstack/react-query";
|
||||||
import dayjs from "dayjs";
|
import dayjs from "dayjs";
|
||||||
import relativeTime from "dayjs/plugin/relativeTime";
|
import relativeTime from "dayjs/plugin/relativeTime";
|
||||||
import { AiOutlinePlus } from "react-icons/ai";
|
import { AiOutlinePlus } from "react-icons/ai";
|
||||||
import { Modal, ModalProps } from "src/components/Modal";
|
import { Modal, ModalProps } from "src/components/Modal";
|
||||||
import { getAllJson } from "src/services/db/json";
|
import { getAllJson } from "src/services/db/json";
|
||||||
|
import useUser from "src/store/useUser";
|
||||||
import styled from "styled-components";
|
import styled from "styled-components";
|
||||||
import { useRouter } from "next/router";
|
|
||||||
|
|
||||||
dayjs.extend(relativeTime);
|
dayjs.extend(relativeTime);
|
||||||
|
|
||||||
@ -91,8 +92,15 @@ const CreateCard: React.FC = () => (
|
|||||||
);
|
);
|
||||||
|
|
||||||
export const CloudModal: React.FC<ModalProps> = ({ visible, setVisible }) => {
|
export const CloudModal: React.FC<ModalProps> = ({ visible, setVisible }) => {
|
||||||
const { query } = useRouter();
|
const { isReady, query } = useRouter();
|
||||||
const { data, isLoading } = useQuery(["allJson", query], () => getAllJson());
|
const user = useUser(state => state.user);
|
||||||
|
const { data, isLoading } = useQuery(
|
||||||
|
["allJson", query, user],
|
||||||
|
() => getAllJson(),
|
||||||
|
{
|
||||||
|
enabled: isReady,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
if (isLoading) return <div>loading</div>;
|
if (isLoading) return <div>loading</div>;
|
||||||
return (
|
return (
|
||||||
@ -109,7 +117,7 @@ export const CloudModal: React.FC<ModalProps> = ({ visible, setVisible }) => {
|
|||||||
preview:
|
preview:
|
||||||
"https://blog.shevarezo.fr/uploads/posts/bulk/FNj3yQLp_visualiser-donnees-json-diagramme-json-crack_rotate3.png",
|
"https://blog.shevarezo.fr/uploads/posts/bulk/FNj3yQLp_visualiser-donnees-json-diagramme-json-crack_rotate3.png",
|
||||||
}}
|
}}
|
||||||
key={json.id}
|
key={json._id}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
<CreateCard />
|
<CreateCard />
|
||||||
|
24
src/containers/Modals/LoginModal/index.tsx
Normal file
24
src/containers/Modals/LoginModal/index.tsx
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
import React from "react";
|
||||||
|
import { altogic } from "src/api/altogic";
|
||||||
|
import { Button } from "src/components/Button";
|
||||||
|
import { Modal, ModalProps } from "src/components/Modal";
|
||||||
|
|
||||||
|
export const LoginModal: React.FC<ModalProps> = ({ setVisible, visible }) => {
|
||||||
|
const handleLoginClick = () => {
|
||||||
|
altogic.auth.signInWithProvider("google");
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal visible={visible} setVisible={setVisible}>
|
||||||
|
<Modal.Header>Login</Modal.Header>
|
||||||
|
<Modal.Content>
|
||||||
|
<h2>Welcome Back!</h2>
|
||||||
|
<p>Login to unlock full potential of JSON Crack!</p>
|
||||||
|
<Button onClick={handleLoginClick} status="SECONDARY" block>
|
||||||
|
Login
|
||||||
|
</Button>
|
||||||
|
</Modal.Content>
|
||||||
|
<Modal.Controls setVisible={setVisible} />
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
};
|
35
src/hooks/useFetchedJson.tsx
Normal file
35
src/hooks/useFetchedJson.tsx
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
import React from "react";
|
||||||
|
import { useRouter } from "next/router";
|
||||||
|
import { useQuery } from "@tanstack/react-query";
|
||||||
|
import { decompressFromBase64 } from "lz-string";
|
||||||
|
import { defaultJson } from "src/constants/data";
|
||||||
|
import { getJson } from "src/services/db/json";
|
||||||
|
import useGraph from "src/store/useGraph";
|
||||||
|
|
||||||
|
export function useJson() {
|
||||||
|
const { query, isReady } = useRouter();
|
||||||
|
const setLoading = useGraph(state => state.setLoading);
|
||||||
|
const setJson = useGraph(state => state.setJson);
|
||||||
|
|
||||||
|
const { data, isLoading, status } = useQuery(
|
||||||
|
["dbJson", query.json],
|
||||||
|
() => getJson(query.json as string),
|
||||||
|
{
|
||||||
|
enabled: isReady && !!query.json,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
if (isReady) {
|
||||||
|
if (query.json) {
|
||||||
|
if (isLoading) return setLoading(true);
|
||||||
|
if (status || !data) setJson(defaultJson);
|
||||||
|
|
||||||
|
if (data?.data) setJson(decompressFromBase64(data.data.json) as string);
|
||||||
|
setLoading(false);
|
||||||
|
} else setJson(defaultJson);
|
||||||
|
}
|
||||||
|
}, [data, isLoading, isReady, query.json, setJson, setLoading, status]);
|
||||||
|
|
||||||
|
return { data };
|
||||||
|
}
|
@ -1,14 +1,9 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import Head from "next/head";
|
import Head from "next/head";
|
||||||
import { useRouter } from "next/router";
|
|
||||||
import { useQuery } from "@tanstack/react-query";
|
|
||||||
import { decompressFromBase64 } from "lz-string";
|
|
||||||
import { Sidebar } from "src/components/Sidebar";
|
import { Sidebar } from "src/components/Sidebar";
|
||||||
import { defaultJson } from "src/constants/data";
|
|
||||||
import { BottomBar } from "src/containers/Editor/BottomBar";
|
import { BottomBar } from "src/containers/Editor/BottomBar";
|
||||||
import Panes from "src/containers/Editor/Panes";
|
import Panes from "src/containers/Editor/Panes";
|
||||||
import { getJson } from "src/services/db/json";
|
import { useJson } from "src/hooks/useFetchedJson";
|
||||||
import useGraph from "src/store/useGraph";
|
|
||||||
import useUser from "src/store/useUser";
|
import useUser from "src/store/useUser";
|
||||||
import styled from "styled-components";
|
import styled from "styled-components";
|
||||||
|
|
||||||
@ -32,36 +27,13 @@ export const StyledEditorWrapper = styled.div`
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
const EditorPage: React.FC = () => {
|
const EditorPage: React.FC = () => {
|
||||||
const { query, isReady } = useRouter();
|
|
||||||
const checkSession = useUser(state => state.checkSession);
|
const checkSession = useUser(state => state.checkSession);
|
||||||
const { data, isLoading, status } = useQuery(
|
useJson();
|
||||||
["dbJson", query.json],
|
|
||||||
() => getJson(query.json as string),
|
|
||||||
{
|
|
||||||
enabled: isReady && !!query.json,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
const setJson = useGraph(state => state.setJson);
|
|
||||||
const setLoading = useGraph(state => state.setLoading);
|
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
checkSession();
|
checkSession();
|
||||||
}, [checkSession]);
|
}, [checkSession]);
|
||||||
|
|
||||||
React.useEffect(() => {
|
|
||||||
if (isReady) {
|
|
||||||
if (query.json) {
|
|
||||||
|
|
||||||
if (isLoading) return setLoading(true);
|
|
||||||
if (status || !data) setJson(defaultJson);
|
|
||||||
|
|
||||||
if (data?.data) setJson(decompressFromBase64(data.data.json) as string);
|
|
||||||
setLoading(false);
|
|
||||||
} else setJson(defaultJson);
|
|
||||||
}
|
|
||||||
}, [data, status, isLoading, isReady, query.json, setJson, setLoading]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<StyledEditorWrapper>
|
<StyledEditorWrapper>
|
||||||
<Head>
|
<Head>
|
||||||
|
@ -13,11 +13,11 @@ interface ConfigActions {
|
|||||||
|
|
||||||
const initialStates = {
|
const initialStates = {
|
||||||
foldNodes: false,
|
foldNodes: false,
|
||||||
hideEditor: false,
|
fullscreen: false,
|
||||||
performanceMode: true,
|
performanceMode: true,
|
||||||
zoomPanPinch: undefined as ReactZoomPanPinchRef | undefined,
|
zoomPanPinch: undefined as ReactZoomPanPinchRef | undefined,
|
||||||
hasChanges: false,
|
hasChanges: false,
|
||||||
hasError: false
|
hasError: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
export type Config = typeof initialStates;
|
export type Config = typeof initialStates;
|
||||||
@ -49,7 +49,14 @@ const useConfig = create<Config & ConfigActions>()((set, get) => ({
|
|||||||
const canvas = document.querySelector(".jsoncrack-canvas") as HTMLElement;
|
const canvas = document.querySelector(".jsoncrack-canvas") as HTMLElement;
|
||||||
if (zoomPanPinch && canvas) zoomPanPinch.zoomToElement(canvas);
|
if (zoomPanPinch && canvas) zoomPanPinch.zoomToElement(canvas);
|
||||||
},
|
},
|
||||||
setConfig: (setting: keyof Config, value: unknown) => set({ [setting]: value }),
|
setConfig: (setting, value) => {
|
||||||
|
if (setting === "fullscreen" && value) {
|
||||||
|
set({ fullscreen: true });
|
||||||
|
return get().centerView();
|
||||||
|
}
|
||||||
|
|
||||||
|
set({ [setting]: value });
|
||||||
|
},
|
||||||
}));
|
}));
|
||||||
|
|
||||||
export default useConfig;
|
export default useConfig;
|
||||||
|
@ -9,7 +9,7 @@ import useConfig from "./useConfig";
|
|||||||
|
|
||||||
const initialStates = {
|
const initialStates = {
|
||||||
json: null as unknown as string,
|
json: null as unknown as string,
|
||||||
loading: false,
|
loading: true,
|
||||||
direction: "RIGHT" as CanvasDirection,
|
direction: "RIGHT" as CanvasDirection,
|
||||||
graphCollapsed: false,
|
graphCollapsed: false,
|
||||||
nodes: [] as NodeData[],
|
nodes: [] as NodeData[],
|
||||||
|
@ -15,11 +15,12 @@ const initialStates = {
|
|||||||
node: false,
|
node: false,
|
||||||
settings: false,
|
settings: false,
|
||||||
share: false,
|
share: false,
|
||||||
|
login: false
|
||||||
};
|
};
|
||||||
|
|
||||||
type ModalType = keyof typeof initialStates;
|
type ModalType = keyof typeof initialStates;
|
||||||
|
|
||||||
const authModals: ModalType[] = ["cloud", "share"];
|
const authModals: ModalType[] = ["cloud", "share", "account"];
|
||||||
|
|
||||||
export type ModalStates = typeof initialStates;
|
export type ModalStates = typeof initialStates;
|
||||||
|
|
||||||
@ -27,7 +28,7 @@ const useModal = create<ModalStates & ModalActions>()(set => ({
|
|||||||
...initialStates,
|
...initialStates,
|
||||||
setVisible: modal => visible => {
|
setVisible: modal => visible => {
|
||||||
if (authModals.includes(modal) && !useUser.getState().isAuthenticated) {
|
if (authModals.includes(modal) && !useUser.getState().isAuthenticated) {
|
||||||
return set({ account: true });
|
return set({ login: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
set({ [modal]: visible });
|
set({ [modal]: visible });
|
||||||
|
@ -2,6 +2,7 @@ import toast from "react-hot-toast";
|
|||||||
import { altogic } from "src/api/altogic";
|
import { altogic } from "src/api/altogic";
|
||||||
import { AltogicAuth } from "src/typings/altogic";
|
import { AltogicAuth } from "src/typings/altogic";
|
||||||
import create from "zustand";
|
import create from "zustand";
|
||||||
|
import useModal from "./useModal";
|
||||||
|
|
||||||
type User = {
|
type User = {
|
||||||
_id: string;
|
_id: string;
|
||||||
@ -32,22 +33,27 @@ const useUser = create<UserStates & UserActions>()(set => ({
|
|||||||
setUser: (key, value) => set({ [key]: value }),
|
setUser: (key, value) => set({ [key]: value }),
|
||||||
logout: () => {
|
logout: () => {
|
||||||
altogic.auth.signOut();
|
altogic.auth.signOut();
|
||||||
|
|
||||||
toast.success("Logged out.");
|
toast.success("Logged out.");
|
||||||
|
useModal.setState({ account: false });
|
||||||
set(initialStates);
|
set(initialStates);
|
||||||
},
|
},
|
||||||
login: response => {
|
login: response => {
|
||||||
set({ user: response.user, isAuthenticated: true });
|
set({ user: response.user, isAuthenticated: true });
|
||||||
},
|
},
|
||||||
checkSession: () => {
|
checkSession: async () => {
|
||||||
const currentSession = altogic.auth.getSession();
|
const currentSession = altogic.auth.getSession();
|
||||||
const currentUser = altogic.auth.getUser();
|
const currentUser = altogic.auth.getUser();
|
||||||
|
|
||||||
if (currentSession) {
|
if (currentSession) {
|
||||||
alert("has");
|
|
||||||
|
|
||||||
altogic.auth.setSession(currentSession);
|
altogic.auth.setSession(currentSession);
|
||||||
set({ user: currentUser as any, isAuthenticated: true });
|
set({ user: currentUser as any, isAuthenticated: true });
|
||||||
|
} else {
|
||||||
|
if (!new URLSearchParams(window.location.search).get("access_token")) return;
|
||||||
|
|
||||||
|
const data = await altogic.auth.getAuthGrant();
|
||||||
|
if (!data.errors?.items.length) {
|
||||||
|
set({ user: data.user as any, isAuthenticated: true });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
|
Loading…
x
Reference in New Issue
Block a user