widget.tsx 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150
  1. import React from "react";
  2. import dynamic from "next/dynamic";
  3. import { useRouter } from "next/router";
  4. import toast from "react-hot-toast";
  5. import { baseURL } from "src/constants/data";
  6. import { darkTheme, lightTheme } from "src/constants/theme";
  7. import { NodeModal } from "src/containers/Modals/NodeModal";
  8. import useGraph from "src/store/useGraph";
  9. import { parser } from "src/utils/jsonParser";
  10. import styled, { ThemeProvider } from "styled-components";
  11. const Graph = dynamic<any>(() => import("src/components/Graph").then(c => c.Graph), {
  12. ssr: false,
  13. });
  14. const StyledAttribute = styled.a`
  15. position: fixed;
  16. bottom: 0;
  17. right: 0;
  18. color: ${({ theme }) => theme.INTERACTIVE_NORMAL};
  19. background: ${({ theme }) => theme.SILVER_DARK};
  20. padding: 4px 8px;
  21. font-size: 14px;
  22. font-weight: 500;
  23. border-radius: 3px 0 0 0;
  24. opacity: 0.8;
  25. @media only screen and (max-width: 768px) {
  26. font-size: 12px;
  27. }
  28. `;
  29. function inIframe() {
  30. try {
  31. return window.self !== window.top;
  32. } catch (e) {
  33. return true;
  34. }
  35. }
  36. interface EmbedMessage {
  37. data: {
  38. json?: string;
  39. options?: any;
  40. };
  41. }
  42. const StyledDeprecated = styled.div`
  43. display: flex;
  44. flex-direction: column;
  45. justify-content: center;
  46. align-items: center;
  47. width: 100%;
  48. height: 100vh;
  49. a {
  50. text-decoration: underline;
  51. }
  52. `;
  53. const WidgetPage = () => {
  54. const { query, push } = useRouter();
  55. const [isModalVisible, setModalVisible] = React.useState(false);
  56. const [selectedNode, setSelectedNode] = React.useState<[string, string][]>([]);
  57. const [theme, setTheme] = React.useState("dark");
  58. const collapsedNodes = useGraph(state => state.collapsedNodes);
  59. const collapsedEdges = useGraph(state => state.collapsedEdges);
  60. const loading = useGraph(state => state.loading);
  61. const setNodeEdges = useGraph(state => state.setNodeEdges);
  62. const setDirection = useGraph(state => state.setDirection);
  63. const openModal = React.useCallback(() => setModalVisible(true), []);
  64. React.useEffect(() => {
  65. const nodeList = collapsedNodes.map(id => `[id$="node-${id}"]`);
  66. const edgeList = collapsedEdges.map(id => `[class$="edge-${id}"]`);
  67. const hiddenItems = document.querySelectorAll(".hide");
  68. hiddenItems.forEach(item => item.classList.remove("hide"));
  69. if (nodeList.length) {
  70. const selectedNodes = document.querySelectorAll(nodeList.join(","));
  71. selectedNodes.forEach(node => node.classList.add("hide"));
  72. }
  73. if (edgeList.length) {
  74. const selectedEdges = document.querySelectorAll(edgeList.join(","));
  75. selectedEdges.forEach(edge => edge.classList.add("hide"));
  76. }
  77. if (!inIframe()) push("/");
  78. }, [collapsedNodes, collapsedEdges, loading, push]);
  79. React.useEffect(() => {
  80. const handler = (event: EmbedMessage) => {
  81. try {
  82. if (!event.data?.json) return;
  83. const { nodes, edges } = parser(event.data.json);
  84. const options = {
  85. direction: "RIGHT",
  86. theme,
  87. ...event.data.options,
  88. };
  89. setDirection(options.direction);
  90. if (options.theme === "light" || options.theme === "dark")
  91. setTheme(options.theme);
  92. setNodeEdges(nodes, edges);
  93. } catch (error) {
  94. console.error(error);
  95. toast.error("Invalid JSON!");
  96. }
  97. };
  98. window.addEventListener("message", handler);
  99. return () => window.removeEventListener("message", handler);
  100. }, [setDirection, setNodeEdges, theme]);
  101. if (query.json)
  102. return (
  103. <StyledDeprecated>
  104. <h1>⚠️ Deprecated ⚠️</h1>
  105. <br />
  106. <a href="https://jsoncrack.com/embed" target="_blank" rel="noreferrer">
  107. https://jsoncrack.com/embed
  108. </a>
  109. </StyledDeprecated>
  110. );
  111. return (
  112. <ThemeProvider theme={theme === "dark" ? darkTheme : lightTheme}>
  113. <Graph openModal={openModal} setSelectedNode={setSelectedNode} isWidget />
  114. <NodeModal
  115. selectedNode={selectedNode}
  116. visible={isModalVisible}
  117. closeModal={() => setModalVisible(false)}
  118. />
  119. <StyledAttribute href={`${baseURL}/editor`} target="_blank" rel="noreferrer">
  120. jsoncrack.com
  121. </StyledAttribute>
  122. </ThemeProvider>
  123. );
  124. };
  125. export default WidgetPage;