styling improvements

This commit is contained in:
AykutSarac 2022-12-11 14:27:07 +03:00
parent 8225bc2986
commit 4ed3411f86
20 changed files with 218 additions and 123 deletions

View File

@ -9,7 +9,7 @@ const withPWA = require("next-pwa")({
* @type {import('next').NextConfig}
*/
const nextConfig = {
reactStrictMode: true,
reactStrictMode: false,
};
module.exports = withPWA(nextConfig);

BIN
public/assets/icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

View File

@ -150,7 +150,7 @@ export const Sidebar: React.FC = () => {
const graphCollapsed = useGraph(state => state.graphCollapsed);
const direction = useGraph(state => state.direction);
const foldNodes = useConfig(state => state.foldNodes);
const hideEditor = useConfig(state => state.hideEditor);
const fullscreen = useConfig(state => state.fullscreen);
const handleSave = () => {
const a = document.createElement("a");
@ -189,7 +189,7 @@ export const Sidebar: React.FC = () => {
</Link>
<Tooltip className="mobile" title="Edit JSON">
<StyledElement onClick={() => setConfig("hideEditor", !hideEditor)}>
<StyledElement onClick={() => setConfig("fullscreen", !fullscreen)}>
<AiOutlineEdit />
</StyledElement>
</Tooltip>

View File

@ -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 16px 32px rgba(0, 0, 0, 0.07), 0 32px 64px rgba(0, 0, 0, 0.07);
opacity: 0.7;
box-sizing: content-box !important;
&:hover {
width: 180px;
@ -43,8 +44,6 @@ const StyledSupportButton = styled.a`
`;
export const SupportButton = () => {
if (location.pathname.includes("widget")) return null;
return (
<StyledSupportButton
href="https://github.com/sponsors/AykutSarac"

View File

@ -22,7 +22,6 @@ const GlobalStyle = createGlobalStyle`
font-family: 'Mona Sans';
font-weight: 400;
font-size: 16px;
scroll-behavior: smooth;
height: 100%;
background-color: #000000;
@ -32,14 +31,15 @@ const GlobalStyle = createGlobalStyle`
@media only screen and (min-width: 768px) {
background-color: #000000;
opacity: 1;
background-image: radial-gradient(#414141 0.5px, #000000 0.5px);
background-size: 15px 15px;
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-attachment: fixed;
background-size: cover;
}
}
* {
-webkit-tap-highlight-color: transparent;
scroll-behavior: smooth;
}
.hide {

View File

@ -1,6 +1,5 @@
import React from "react";
import { useRouter } from "next/router";
import { useQuery } from "@tanstack/react-query";
import toast from "react-hot-toast";
import {
AiOutlineCloudSync,
@ -10,11 +9,12 @@ import {
AiOutlineUnlock,
} from "react-icons/ai";
import { VscAccount } from "react-icons/vsc";
import { altogic } from "src/api/altogic";
import { getJson, saveJson, updateJson } from "src/services/db/json";
import { useJson } from "src/hooks/useFetchedJson";
import { saveJson, updateJson } from "src/services/db/json";
import useConfig from "src/store/useConfig";
import useGraph from "src/store/useGraph";
import useModal from "src/store/useModal";
import useStored from "src/store/useStored";
import useUser from "src/store/useUser";
import styled from "styled-components";
@ -61,29 +61,30 @@ const StyledBottomBarItem = styled.button`
}
`;
export const BottomBar = () => {
const { isReady, replace, query } = useRouter();
const StyledImg = styled.img<{ light: boolean }>`
filter: ${({ light }) => light && "invert(100%)"};
`;
const { data } = useQuery(
["dbJson", query.json],
() => getJson(query.json as string),
{
enabled: isReady && !!query.json,
}
);
export const BottomBar = () => {
const { replace, query } = useRouter();
const { data } = useJson();
const user = useUser(state => state.user);
const setVisible = useModal(state => state.setVisible);
const getJsonState = useGraph(state => state.getJson);
const hasChanges = useConfig(state => state.hasChanges);
const setConfig = useConfig(state => state.setConfig);
const lightmode = useStored(state => state.lightmode);
const [isPrivate, setIsPrivate] = React.useState(true);
React.useEffect(() => {
if (data) setIsPrivate(data.data.private);
console.log(data);
setIsPrivate(data?.data.private ?? true);
}, [data]);
const handleSaveJson = React.useCallback(() => {
if (!user) return setVisible("login")(true);
if (hasChanges) {
toast.promise(
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 = () => {
if (user) return setVisible("account")(true);
altogic.auth.signInWithProvider("google");
else setVisible("login")(true);
};
const setPrivate = () => {
if (!query.json) return handleSaveJson();
updateJson(query.json as string, { private: !isPrivate });
setIsPrivate(!isPrivate);
};
return (
@ -121,26 +121,35 @@ export const BottomBar = () => {
{hasChanges ? <AiOutlineCloudUpload /> : <AiOutlineCloudSync />}
{hasChanges ? "Unsaved Changes" : "Saved"}
</StyledBottomBarItem>
{query.json && (
<>
<StyledBottomBarItem onClick={setPrivate}>
{isPrivate ? <AiOutlineLock /> : <AiOutlineUnlock />}
{isPrivate ? "Private" : "Public"}
</StyledBottomBarItem>
{query.json && (
<StyledBottomBarItem onClick={() => setVisible("share")(true)}>
<AiOutlineLink />
Share
</StyledBottomBarItem>
</>
)}
</StyledLeft>
<StyledRight>
<a
href="https://www.altogic.com/?utm_source=jsoncrack&utm_medium=referral&utm_campaign=sponsorship"
rel="sponsored noreferrer"
target="_blank"
>
<StyledBottomBarItem>
Powered by
<img
<StyledImg
height="20"
src="https://regexlearn.com/altogic.svg"
alt="powered by buildable"
light={lightmode}
/>
</StyledBottomBarItem>
</a>
</StyledRight>
</StyledBottomBar>
);

View File

@ -17,25 +17,25 @@ const LiveEditor = dynamic(() => import("src/containers/Editor/LiveEditor"), {
});
const Panes: React.FC = () => {
const hideEditor = useConfig(state => state.hideEditor);
const fullscreen = useConfig(state => state.fullscreen);
const setConfig = useConfig(state => state.setConfig);
const isMobile = window.innerWidth <= 768;
React.useEffect(() => {
if (isMobile) setConfig("hideEditor", true);
if (isMobile) setConfig("fullscreen", true);
}, [isMobile, setConfig]);
return (
<StyledEditor proportionalLayout={false} vertical={isMobile}>
<Allotment.Pane
preferredSize={isMobile ? "100%" : 400}
minSize={hideEditor ? 0 : 300}
minSize={fullscreen ? 0 : 300}
maxSize={isMobile ? Infinity : 800}
visible={!hideEditor}
visible={!fullscreen}
>
<JsonEditor />
</Allotment.Pane>
<Allotment.Pane minSize={0} maxSize={isMobile && !hideEditor ? 0 : Infinity}>
<Allotment.Pane minSize={0} maxSize={isMobile && !fullscreen ? 0 : Infinity}>
<LiveEditor />
</Allotment.Pane>
</StyledEditor>

View File

@ -48,13 +48,13 @@ const StyledToolElement = styled.button`
export const Tools: React.FC = () => {
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 zoomIn = useConfig(state => state.zoomIn);
const zoomOut = useConfig(state => state.zoomOut);
const centerView = useConfig(state => state.centerView);
const toggleEditor = () => setConfig("hideEditor", !hideEditor);
const toggleEditor = () => setConfig("fullscreen", !fullscreen);
return (
<>

View File

@ -2,6 +2,7 @@ import React from "react";
import Head from "next/head";
import Link from "next/link";
import Script from "next/script";
import { AiOutlineRight } from "react-icons/ai";
import { FaGithub, FaHeart, FaLinkedin, FaTwitter } from "react-icons/fa";
import {
HiCursorClick,
@ -51,10 +52,10 @@ const HeroSection = () => {
<Styles.StyledHighlightedText>instantly</Styles.StyledHighlightedText> into
graphs.
</Styles.StyledSubTitle>
<Styles.StyledMinorTitle>Paste - Import - Fetch!</Styles.StyledMinorTitle>
<Styles.StyledButton rel="prefetch" href="/editor" link>
GO TO EDITOR
<AiOutlineRight strokeWidth="30px" />
</Styles.StyledButton>
<Styles.StyledButtonWrapper>
@ -238,8 +239,8 @@ const EmbedSection = () => (
json: defaultJson,
options: {
theme: "dark",
direction: "DOWN"
}
direction: "DOWN",
},
},
"*"
);
@ -288,7 +289,8 @@ const SponsorSection = () => (
const Footer = () => (
<Styles.StyledFooter>
<Styles.StyledFooterText>
© 2022 JSON Crack - {pkg.version}
© <img width="100" src="assets/icon.png" alt="icon" />
{new Date().getFullYear()} - {pkg.version}
</Styles.StyledFooterText>
<Styles.StyledIconLinks>
<Styles.StyledNavLink
@ -335,8 +337,8 @@ const Home: React.FC = () => {
<EmbedSection />
<SupportSection />
<SponsorSection />
<Footer />
<SupportButton />
<Footer />
</Styles.StyledHome>
);
};

View File

@ -137,6 +137,7 @@ export const StyledSubTitle = styled.h2`
font-size: 2.5rem;
max-width: 40rem;
margin: 0;
margin-bottom: 25px;
@media only screen and (max-width: 768px) {
font-size: 1.5rem;
@ -158,11 +159,12 @@ export const StyledMinorTitle = styled.p`
export const StyledButton = styled(Button)`
background: ${({ status }) => !status && "#a13cc2"};
padding: 12px 24px;
height: 46px;
div {
font-family: "Roboto", sans-serif;
font-family: "Mona Sans";
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`
display: flex;
max-width: 85%;
max-width: 60%;
margin: 0 auto;
gap: 2rem;
padding: 50px 3%;
padding: 0 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) {
flex-direction: column;
@ -211,6 +217,16 @@ export const StyledFeaturesSection = styled.section`
export const StyledSectionCard = styled.div`
text-align: center;
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`
@ -350,6 +366,9 @@ export const StyledFooter = styled.footer`
`;
export const StyledFooterText = styled.p`
display: flex;
align-items: center;
gap: 5px;
color: #b4b4b4;
`;

View File

@ -4,23 +4,48 @@ import { ClearModal } from "src/containers/Modals/ClearModal";
import { CloudModal } from "src/containers/Modals/CloudModal";
import { DownloadModal } from "src/containers/Modals/DownloadModal";
import { ImportModal } from "src/containers/Modals/ImportModal";
import { LoginModal } from "src/containers/Modals/LoginModal";
import { SettingsModal } from "src/containers/Modals/SettingsModal";
import { ShareModal } from "src/containers/Modals/ShareModal";
import useModal from "src/store/useModal";
import shallow from "zustand/shallow";
export const ModalController = () => {
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 (
<>
<ImportModal visible={state.import} setVisible={setVisible("import")} />
<ClearModal visible={state.clear} setVisible={setVisible("clear")} />
<DownloadModal visible={state.download} setVisible={setVisible("download")} />
<SettingsModal visible={state.settings} setVisible={setVisible("settings")} />
<CloudModal visible={state.cloud} setVisible={setVisible("cloud")} />
<AccountModal visible={state.account} setVisible={setVisible("account")} />
<ShareModal visible={state.share} setVisible={setVisible("share")} />
<ImportModal visible={importModal} setVisible={setVisible("import")} />
<ClearModal visible={clearModal} setVisible={setVisible("clear")} />
<DownloadModal visible={downloadModal} setVisible={setVisible("download")} />
<SettingsModal visible={settingsModal} setVisible={setVisible("settings")} />
<CloudModal visible={cloudModal} setVisible={setVisible("cloud")} />
<AccountModal visible={accountModal} setVisible={setVisible("account")} />
<LoginModal visible={loginModal} setVisible={setVisible("login")} />
<ShareModal visible={shareModal} setVisible={setVisible("share")} />
</>
);
};

View File

@ -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`
display: flex;
flex-wrap: wrap;

View File

@ -1,12 +1,13 @@
import React from "react";
import { useRouter } from "next/router";
import { useQuery } from "@tanstack/react-query";
import dayjs from "dayjs";
import relativeTime from "dayjs/plugin/relativeTime";
import { AiOutlinePlus } from "react-icons/ai";
import { Modal, ModalProps } from "src/components/Modal";
import { getAllJson } from "src/services/db/json";
import useUser from "src/store/useUser";
import styled from "styled-components";
import { useRouter } from "next/router";
dayjs.extend(relativeTime);
@ -91,8 +92,15 @@ const CreateCard: React.FC = () => (
);
export const CloudModal: React.FC<ModalProps> = ({ visible, setVisible }) => {
const { query } = useRouter();
const { data, isLoading } = useQuery(["allJson", query], () => getAllJson());
const { isReady, query } = useRouter();
const user = useUser(state => state.user);
const { data, isLoading } = useQuery(
["allJson", query, user],
() => getAllJson(),
{
enabled: isReady,
}
);
if (isLoading) return <div>loading</div>;
return (
@ -109,7 +117,7 @@ export const CloudModal: React.FC<ModalProps> = ({ visible, setVisible }) => {
preview:
"https://blog.shevarezo.fr/uploads/posts/bulk/FNj3yQLp_visualiser-donnees-json-diagramme-json-crack_rotate3.png",
}}
key={json.id}
key={json._id}
/>
))}
<CreateCard />

View 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>
);
};

View 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 };
}

View File

@ -1,14 +1,9 @@
import React from "react";
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 { defaultJson } from "src/constants/data";
import { BottomBar } from "src/containers/Editor/BottomBar";
import Panes from "src/containers/Editor/Panes";
import { getJson } from "src/services/db/json";
import useGraph from "src/store/useGraph";
import { useJson } from "src/hooks/useFetchedJson";
import useUser from "src/store/useUser";
import styled from "styled-components";
@ -32,36 +27,13 @@ export const StyledEditorWrapper = styled.div`
`;
const EditorPage: React.FC = () => {
const { query, isReady } = useRouter();
const checkSession = useUser(state => state.checkSession);
const { data, isLoading, status } = useQuery(
["dbJson", query.json],
() => getJson(query.json as string),
{
enabled: isReady && !!query.json,
}
);
const setJson = useGraph(state => state.setJson);
const setLoading = useGraph(state => state.setLoading);
useJson();
React.useEffect(() => {
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 (
<StyledEditorWrapper>
<Head>

View File

@ -13,11 +13,11 @@ interface ConfigActions {
const initialStates = {
foldNodes: false,
hideEditor: false,
fullscreen: false,
performanceMode: true,
zoomPanPinch: undefined as ReactZoomPanPinchRef | undefined,
hasChanges: false,
hasError: false
hasError: false,
};
export type Config = typeof initialStates;
@ -49,7 +49,14 @@ const useConfig = create<Config & ConfigActions>()((set, get) => ({
const canvas = document.querySelector(".jsoncrack-canvas") as HTMLElement;
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;

View File

@ -9,7 +9,7 @@ import useConfig from "./useConfig";
const initialStates = {
json: null as unknown as string,
loading: false,
loading: true,
direction: "RIGHT" as CanvasDirection,
graphCollapsed: false,
nodes: [] as NodeData[],

View File

@ -15,11 +15,12 @@ const initialStates = {
node: false,
settings: false,
share: false,
login: false
};
type ModalType = keyof typeof initialStates;
const authModals: ModalType[] = ["cloud", "share"];
const authModals: ModalType[] = ["cloud", "share", "account"];
export type ModalStates = typeof initialStates;
@ -27,7 +28,7 @@ const useModal = create<ModalStates & ModalActions>()(set => ({
...initialStates,
setVisible: modal => visible => {
if (authModals.includes(modal) && !useUser.getState().isAuthenticated) {
return set({ account: true });
return set({ login: true });
}
set({ [modal]: visible });

View File

@ -2,6 +2,7 @@ import toast from "react-hot-toast";
import { altogic } from "src/api/altogic";
import { AltogicAuth } from "src/typings/altogic";
import create from "zustand";
import useModal from "./useModal";
type User = {
_id: string;
@ -32,22 +33,27 @@ const useUser = create<UserStates & UserActions>()(set => ({
setUser: (key, value) => set({ [key]: value }),
logout: () => {
altogic.auth.signOut();
toast.success("Logged out.");
useModal.setState({ account: false });
set(initialStates);
},
login: response => {
set({ user: response.user, isAuthenticated: true });
},
checkSession: () => {
checkSession: async () => {
const currentSession = altogic.auth.getSession();
const currentUser = altogic.auth.getUser();
if (currentSession) {
alert("has");
altogic.auth.setSession(currentSession);
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 });
}
}
},
}));