BLUR-27
update gitignore (remove .web dir)
This commit is contained in:
parent
6eebdd597a
commit
4e3288c0e5
39
.web/.gitignore
vendored
39
.web/.gitignore
vendored
@ -1,39 +0,0 @@
|
|||||||
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
|
||||||
|
|
||||||
/_static
|
|
||||||
|
|
||||||
# dependencies
|
|
||||||
/node_modules
|
|
||||||
/.pnp
|
|
||||||
.pnp.js
|
|
||||||
|
|
||||||
# testing
|
|
||||||
/coverage
|
|
||||||
|
|
||||||
# next.js
|
|
||||||
/.next/
|
|
||||||
/out/
|
|
||||||
|
|
||||||
# production
|
|
||||||
/build
|
|
||||||
|
|
||||||
# misc
|
|
||||||
.DS_Store
|
|
||||||
*.pem
|
|
||||||
|
|
||||||
# debug
|
|
||||||
npm-debug.log*
|
|
||||||
yarn-debug.log*
|
|
||||||
yarn-error.log*
|
|
||||||
|
|
||||||
# local env files
|
|
||||||
.env.local
|
|
||||||
.env.development.local
|
|
||||||
.env.test.local
|
|
||||||
.env.production.local
|
|
||||||
|
|
||||||
# vercel
|
|
||||||
.vercel
|
|
||||||
|
|
||||||
# DS_Store
|
|
||||||
.DS_Store
|
|
@ -1,21 +0,0 @@
|
|||||||
import { useColorMode as chakraUseColorMode } from "@chakra-ui/react"
|
|
||||||
import { useTheme } from "next-themes"
|
|
||||||
import { useEffect } from "react"
|
|
||||||
import { ColorModeContext } from "/utils/context.js"
|
|
||||||
|
|
||||||
export default function ChakraColorModeProvider({ children }) {
|
|
||||||
const {colorMode, toggleColorMode} = chakraUseColorMode()
|
|
||||||
const {theme, setTheme} = useTheme()
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (colorMode != theme) {
|
|
||||||
toggleColorMode()
|
|
||||||
}
|
|
||||||
}, [theme])
|
|
||||||
|
|
||||||
return (
|
|
||||||
<ColorModeContext.Provider value={[ colorMode, toggleColorMode ]}>
|
|
||||||
{children}
|
|
||||||
</ColorModeContext.Provider>
|
|
||||||
)
|
|
||||||
}
|
|
@ -1,20 +0,0 @@
|
|||||||
import { useTheme } from "next-themes"
|
|
||||||
import { useEffect, useState } from "react"
|
|
||||||
import { ColorModeContext } from "/utils/context.js"
|
|
||||||
|
|
||||||
|
|
||||||
export default function RadixThemesColorModeProvider({ children }) {
|
|
||||||
const {theme, setTheme} = useTheme()
|
|
||||||
const [colorMode, setColorMode] = useState("light")
|
|
||||||
|
|
||||||
useEffect(() => setColorMode(theme), [theme])
|
|
||||||
|
|
||||||
const toggleColorMode = () => {
|
|
||||||
setTheme(theme === "light" ? "dark" : "light")
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
<ColorModeContext.Provider value={[ colorMode, toggleColorMode ]}>
|
|
||||||
{children}
|
|
||||||
</ColorModeContext.Provider>
|
|
||||||
)
|
|
||||||
}
|
|
@ -1 +0,0 @@
|
|||||||
{"PING": "http://localhost:8000/ping", "EVENT": "ws://localhost:8000/_event", "UPLOAD": "http://localhost:8000/_upload"}
|
|
@ -1,8 +0,0 @@
|
|||||||
{
|
|
||||||
"compilerOptions": {
|
|
||||||
"baseUrl": ".",
|
|
||||||
"paths": {
|
|
||||||
"@/*": ["public/*"]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,6 +0,0 @@
|
|||||||
module.exports = {
|
|
||||||
basePath: "",
|
|
||||||
compress: true,
|
|
||||||
reactStrictMode: true,
|
|
||||||
trailingSlash: true,
|
|
||||||
};
|
|
3686
.web/package-lock.json
generated
3686
.web/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -1,33 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "reflex",
|
|
||||||
"scripts": {
|
|
||||||
"dev": "next dev",
|
|
||||||
"export": "next build && next export -o _static",
|
|
||||||
"export-sitemap": "next build && next-sitemap && next export -o _static",
|
|
||||||
"prod": "next start"
|
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"@chakra-ui/icons": "^2.0.19",
|
|
||||||
"@chakra-ui/react": "^2.6.1",
|
|
||||||
"@chakra-ui/system": "^2.5.7",
|
|
||||||
"@emotion/react": "^11.11.0",
|
|
||||||
"@emotion/styled": "^11.11.0",
|
|
||||||
"axios": "1.4.0",
|
|
||||||
"focus-visible": "5.2.0",
|
|
||||||
"framer-motion": "^10.16.4",
|
|
||||||
"json5": "2.2.3",
|
|
||||||
"next": "13.5.4",
|
|
||||||
"next-sitemap": "4.1.8",
|
|
||||||
"next-themes": "0.2.0",
|
|
||||||
"react": "18.2.0",
|
|
||||||
"react-dom": "18.2.0",
|
|
||||||
"react-dropzone": "^14.2.3",
|
|
||||||
"socket.io-client": "4.6.1",
|
|
||||||
"tailwindcss": "^3.3.2",
|
|
||||||
"universal-cookie": "4.0.4"
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
|
||||||
"autoprefixer": "10.4.14",
|
|
||||||
"postcss": "8.4.24"
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,88 +0,0 @@
|
|||||||
import { Fragment, useContext, useEffect, useRef, useState } from "react"
|
|
||||||
import { useRouter } from "next/router"
|
|
||||||
import { Event, getAllLocalStorageItems, getRefValue, getRefValues, isTrue, preventDefault, refs, spreadArraysOrObjects, uploadFiles, useEventLoop } from "/utils/state"
|
|
||||||
import { ColorModeContext, EventLoopContext, initialEvents, StateContext } from "/utils/context.js"
|
|
||||||
import "focus-visible/dist/focus-visible"
|
|
||||||
import { Modal, ModalBody, ModalContent, ModalHeader, ModalOverlay, Text } from "@chakra-ui/react"
|
|
||||||
import { getEventURL } from "/utils/state.js"
|
|
||||||
import { useClientSideRouting } from "/utils/client_side_routing"
|
|
||||||
import Error from "next/error"
|
|
||||||
import NextHead from "next/head"
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
export default function Component() {
|
|
||||||
const state = useContext(StateContext)
|
|
||||||
const router = useRouter()
|
|
||||||
const [ colorMode, toggleColorMode ] = useContext(ColorModeContext)
|
|
||||||
const focusRef = useRef();
|
|
||||||
|
|
||||||
// Main event loop.
|
|
||||||
const [addEvents, connectError] = useContext(EventLoopContext)
|
|
||||||
|
|
||||||
// Set focus to the specified element.
|
|
||||||
useEffect(() => {
|
|
||||||
if (focusRef.current) {
|
|
||||||
focusRef.current.focus();
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
// Route after the initial page hydration.
|
|
||||||
useEffect(() => {
|
|
||||||
const change_complete = () => addEvents(initialEvents())
|
|
||||||
router.events.on('routeChangeComplete', change_complete)
|
|
||||||
return () => {
|
|
||||||
router.events.off('routeChangeComplete', change_complete)
|
|
||||||
}
|
|
||||||
}, [router])
|
|
||||||
|
|
||||||
const routeNotFound = useClientSideRouting()
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Fragment>
|
|
||||||
<Fragment>
|
|
||||||
{isTrue(connectError !== null) ? (
|
|
||||||
<Fragment>
|
|
||||||
<Modal isOpen={connectError !== null}>
|
|
||||||
<ModalOverlay>
|
|
||||||
<ModalContent>
|
|
||||||
<ModalHeader>
|
|
||||||
{`Connection Error`}
|
|
||||||
</ModalHeader>
|
|
||||||
<ModalBody>
|
|
||||||
<Text>
|
|
||||||
{`Cannot connect to server: `}
|
|
||||||
{(connectError !== null) ? connectError.message : ''}
|
|
||||||
{`. Check if server is reachable at `}
|
|
||||||
{getEventURL().href}
|
|
||||||
</Text>
|
|
||||||
</ModalBody>
|
|
||||||
</ModalContent>
|
|
||||||
</ModalOverlay>
|
|
||||||
</Modal>
|
|
||||||
</Fragment>
|
|
||||||
) : (
|
|
||||||
<Fragment/>
|
|
||||||
)}
|
|
||||||
</Fragment>
|
|
||||||
<Fragment>
|
|
||||||
{isTrue(routeNotFound) ? (
|
|
||||||
<Fragment>
|
|
||||||
<Error statusCode={404}/>
|
|
||||||
</Fragment>
|
|
||||||
) : (
|
|
||||||
<Fragment>
|
|
||||||
|
|
||||||
</Fragment>
|
|
||||||
)}
|
|
||||||
</Fragment>
|
|
||||||
<NextHead>
|
|
||||||
<title>
|
|
||||||
{`404 - Not Found`}
|
|
||||||
</title>
|
|
||||||
<meta content={`The page was not found`} name={`description`}/>
|
|
||||||
<meta content={`favicon.ico`} property={`og:image`}/>
|
|
||||||
</NextHead>
|
|
||||||
</Fragment>
|
|
||||||
)
|
|
||||||
}
|
|
@ -1,46 +0,0 @@
|
|||||||
import { ChakraProvider, extendTheme } from "@chakra-ui/react"
|
|
||||||
import theme from "/utils/theme.js"
|
|
||||||
import { css, Global } from "@emotion/react"
|
|
||||||
import ChakraColorModeProvider from "/components/reflex/chakra_color_mode_provider.js"
|
|
||||||
|
|
||||||
|
|
||||||
import { EventLoopProvider } from "/utils/context.js";
|
|
||||||
import { ThemeProvider } from 'next-themes'
|
|
||||||
|
|
||||||
|
|
||||||
import '/styles/styles.css'
|
|
||||||
|
|
||||||
const GlobalStyles = css`
|
|
||||||
/* Hide the blue border around Chakra components. */
|
|
||||||
.js-focus-visible :focus:not([data-focus-visible-added]) {
|
|
||||||
outline: none;
|
|
||||||
box-shadow: none;
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
|
|
||||||
function AppWrap({children}) {
|
|
||||||
|
|
||||||
|
|
||||||
return (
|
|
||||||
<ChakraProvider theme={extendTheme(theme)}>
|
|
||||||
<Global styles={GlobalStyles}/>
|
|
||||||
<ChakraColorModeProvider>
|
|
||||||
{children}
|
|
||||||
</ChakraColorModeProvider>
|
|
||||||
</ChakraProvider>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function MyApp({ Component, pageProps }) {
|
|
||||||
return (
|
|
||||||
<ThemeProvider defaultTheme="light" storageKey="chakra-ui-color-mode" attribute="class">
|
|
||||||
<AppWrap>
|
|
||||||
<EventLoopProvider>
|
|
||||||
<Component {...pageProps} />
|
|
||||||
</EventLoopProvider>
|
|
||||||
</AppWrap>
|
|
||||||
</ThemeProvider>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
@ -1,15 +0,0 @@
|
|||||||
import { Head, Html, Main, NextScript } from "next/document"
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
export default function Document() {
|
|
||||||
return (
|
|
||||||
<Html>
|
|
||||||
<Head/>
|
|
||||||
<body>
|
|
||||||
<Main/>
|
|
||||||
<NextScript/>
|
|
||||||
</body>
|
|
||||||
</Html>
|
|
||||||
)
|
|
||||||
}
|
|
@ -1,164 +0,0 @@
|
|||||||
import { Fragment, useContext, useEffect, useRef, useState } from "react"
|
|
||||||
import { useRouter } from "next/router"
|
|
||||||
import { Event, getAllLocalStorageItems, getRefValue, getRefValues, isTrue, preventDefault, refs, spreadArraysOrObjects, uploadFiles, useEventLoop } from "/utils/state"
|
|
||||||
import { ColorModeContext, EventLoopContext, initialEvents, StateContext } from "/utils/context.js"
|
|
||||||
import "focus-visible/dist/focus-visible"
|
|
||||||
import { Box, Code, Heading, HStack, Image, Link, Menu, MenuButton, MenuDivider, MenuItem, MenuList, Modal, ModalBody, ModalContent, ModalHeader, ModalOverlay, Spacer, Text, VStack } from "@chakra-ui/react"
|
|
||||||
import { getEventURL } from "/utils/state.js"
|
|
||||||
import NextLink from "next/link"
|
|
||||||
import { HamburgerIcon } from "@chakra-ui/icons"
|
|
||||||
import NextHead from "next/head"
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
export default function Component() {
|
|
||||||
const state = useContext(StateContext)
|
|
||||||
const router = useRouter()
|
|
||||||
const [ colorMode, toggleColorMode ] = useContext(ColorModeContext)
|
|
||||||
const focusRef = useRef();
|
|
||||||
|
|
||||||
// Main event loop.
|
|
||||||
const [addEvents, connectError] = useContext(EventLoopContext)
|
|
||||||
|
|
||||||
// Set focus to the specified element.
|
|
||||||
useEffect(() => {
|
|
||||||
if (focusRef.current) {
|
|
||||||
focusRef.current.focus();
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
// Route after the initial page hydration.
|
|
||||||
useEffect(() => {
|
|
||||||
const change_complete = () => addEvents(initialEvents())
|
|
||||||
router.events.on('routeChangeComplete', change_complete)
|
|
||||||
return () => {
|
|
||||||
router.events.off('routeChangeComplete', change_complete)
|
|
||||||
}
|
|
||||||
}, [router])
|
|
||||||
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Fragment>
|
|
||||||
<Fragment>
|
|
||||||
{isTrue(connectError !== null) ? (
|
|
||||||
<Fragment>
|
|
||||||
<Modal isOpen={connectError !== null}>
|
|
||||||
<ModalOverlay>
|
|
||||||
<ModalContent>
|
|
||||||
<ModalHeader>
|
|
||||||
{`Connection Error`}
|
|
||||||
</ModalHeader>
|
|
||||||
<ModalBody>
|
|
||||||
<Text>
|
|
||||||
{`Cannot connect to server: `}
|
|
||||||
{(connectError !== null) ? connectError.message : ''}
|
|
||||||
{`. Check if server is reachable at `}
|
|
||||||
{getEventURL().href}
|
|
||||||
</Text>
|
|
||||||
</ModalBody>
|
|
||||||
</ModalContent>
|
|
||||||
</ModalOverlay>
|
|
||||||
</Modal>
|
|
||||||
</Fragment>
|
|
||||||
) : (
|
|
||||||
<Fragment/>
|
|
||||||
)}
|
|
||||||
</Fragment>
|
|
||||||
<HStack alignItems={`flex-start`} sx={{"transition": "left 0.5s, width 0.5s", "position": "relative"}}>
|
|
||||||
<Box sx={{"display": ["none", "none", "block"], "minWidth": "20em", "height": "100%", "position": "sticky", "top": "0px", "borderRight": "1px solid #F4F3F6"}}>
|
|
||||||
<VStack sx={{"height": "100dvh"}}>
|
|
||||||
<HStack sx={{"width": "100%", "borderBottom": "1px solid #F4F3F6", "padding": "1em"}}>
|
|
||||||
<Image src={`/icon.png`} sx={{"height": "4.5em"}}/>
|
|
||||||
<Spacer/>
|
|
||||||
</HStack>
|
|
||||||
<VStack alignItems={`flex-start`} sx={{"width": "100%", "overflowY": "auto", "padding": "1em"}}>
|
|
||||||
<Link as={NextLink} href={`/`} sx={{"width": "100%"}}>
|
|
||||||
<HStack sx={{"bg": isTrue((state.router.page.path === "/strona g\\u0142\\u00f3wna") || (((state.router.page.path === "/") && "Strona g\\u0142\\u00f3wna") === "Home")) ? `#fff0f7` : `transparent`, "color": isTrue((state.router.page.path === "/strona g\\u0142\\u00f3wna") || (((state.router.page.path === "/") && "Strona g\\u0142\\u00f3wna") === "Home")) ? `#1A1060` : `black`, "borderRadius": "0.375rem", "boxShadow": "0px 0px 0px 1px rgba(84, 82, 95, 0.14)", "width": "100%", "paddingX": "1em", "height": "2em"}}>
|
|
||||||
<Text>
|
|
||||||
{`Strona główna`}
|
|
||||||
</Text>
|
|
||||||
</HStack>
|
|
||||||
</Link>
|
|
||||||
<Link as={NextLink} href={`/dashboard`} sx={{"width": "100%"}}>
|
|
||||||
<HStack sx={{"bg": isTrue((state.router.page.path === "/dashboard") || (((state.router.page.path === "/") && "Dashboard") === "Home")) ? `#fff0f7` : `transparent`, "color": isTrue((state.router.page.path === "/dashboard") || (((state.router.page.path === "/") && "Dashboard") === "Home")) ? `#1A1060` : `black`, "borderRadius": "0.375rem", "boxShadow": "0px 0px 0px 1px rgba(84, 82, 95, 0.14)", "width": "100%", "paddingX": "1em", "height": "2em"}}>
|
|
||||||
<Text>
|
|
||||||
{`Dashboard`}
|
|
||||||
</Text>
|
|
||||||
</HStack>
|
|
||||||
</Link>
|
|
||||||
<Link as={NextLink} href={`/settings`} sx={{"width": "100%"}}>
|
|
||||||
<HStack sx={{"bg": isTrue((state.router.page.path === "/settings") || (((state.router.page.path === "/") && "Settings") === "Home")) ? `#fff0f7` : `transparent`, "color": isTrue((state.router.page.path === "/settings") || (((state.router.page.path === "/") && "Settings") === "Home")) ? `#1A1060` : `black`, "borderRadius": "0.375rem", "boxShadow": "0px 0px 0px 1px rgba(84, 82, 95, 0.14)", "width": "100%", "paddingX": "1em", "height": "2em"}}>
|
|
||||||
<Text>
|
|
||||||
{`Settings`}
|
|
||||||
</Text>
|
|
||||||
</HStack>
|
|
||||||
</Link>
|
|
||||||
</VStack>
|
|
||||||
<Spacer/>
|
|
||||||
</VStack>
|
|
||||||
</Box>
|
|
||||||
<Box sx={{"paddingTop": "5em", "paddingX": ["auto", "2em"], "flex": "1"}}>
|
|
||||||
<Box sx={{"alignItems": "flex-start", "boxShadow": "0px 0px 0px 1px rgba(84, 82, 95, 0.14)", "borderRadius": "0.375rem", "padding": "1em", "marginBottom": "2em"}}>
|
|
||||||
<VStack>
|
|
||||||
<Heading sx={{"fontSize": "3em"}}>
|
|
||||||
{`Dashboard`}
|
|
||||||
</Heading>
|
|
||||||
<Text>
|
|
||||||
{`Welcome to Reflex!`}
|
|
||||||
</Text>
|
|
||||||
<Text>
|
|
||||||
{`You can edit this page in `}
|
|
||||||
<Code>
|
|
||||||
{`{your_app}/pages/dashboard.py`}
|
|
||||||
</Code>
|
|
||||||
</Text>
|
|
||||||
</VStack>
|
|
||||||
</Box>
|
|
||||||
</Box>
|
|
||||||
<Box sx={{"position": "fixed", "right": "1.5em", "top": "1.5em", "zIndex": "500"}}>
|
|
||||||
<Menu>
|
|
||||||
<MenuButton sx={{"width": "3em", "height": "3em", "backgroundColor": "white", "border": "1px solid #F4F3F6", "borderRadius": "0.375rem"}}>
|
|
||||||
<HamburgerIcon sx={{"size": "4em", "color": "black"}}/>
|
|
||||||
</MenuButton>
|
|
||||||
<MenuList>
|
|
||||||
<MenuItem sx={{"_hover": {"bg": "#fff0f7"}}}>
|
|
||||||
<Link as={NextLink} href={`/`} sx={{"width": "100%"}}>
|
|
||||||
{`Strona główna`}
|
|
||||||
</Link>
|
|
||||||
</MenuItem>
|
|
||||||
<MenuItem sx={{"_hover": {"bg": "#fff0f7"}}}>
|
|
||||||
<Link as={NextLink} href={`/dashboard`} sx={{"width": "100%"}}>
|
|
||||||
{`Dashboard`}
|
|
||||||
</Link>
|
|
||||||
</MenuItem>
|
|
||||||
<MenuItem sx={{"_hover": {"bg": "#fff0f7"}}}>
|
|
||||||
<Link as={NextLink} href={`/settings`} sx={{"width": "100%"}}>
|
|
||||||
{`Settings`}
|
|
||||||
</Link>
|
|
||||||
</MenuItem>
|
|
||||||
<MenuDivider/>
|
|
||||||
<MenuItem sx={{"_hover": {"bg": "#fff0f7"}}}>
|
|
||||||
<Link as={NextLink} href={`https://github.com/reflex-dev`} sx={{"width": "100%"}}>
|
|
||||||
{`About`}
|
|
||||||
</Link>
|
|
||||||
</MenuItem>
|
|
||||||
<MenuItem sx={{"_hover": {"bg": "#fff0f7"}}}>
|
|
||||||
<Link as={NextLink} href={`mailto:founders@=reflex.dev`} sx={{"width": "100%"}}>
|
|
||||||
{`Contact`}
|
|
||||||
</Link>
|
|
||||||
</MenuItem>
|
|
||||||
</MenuList>
|
|
||||||
</Menu>
|
|
||||||
</Box>
|
|
||||||
</HStack>
|
|
||||||
<NextHead>
|
|
||||||
<title>
|
|
||||||
{`Dashboard`}
|
|
||||||
</title>
|
|
||||||
<meta content={`A Reflex app.`} name={`description`}/>
|
|
||||||
<meta content={`favicon.ico`} property={`og:image`}/>
|
|
||||||
<meta content={`width=device-width, shrink-to-fit=no, initial-scale=1`} name={`viewport`}/>
|
|
||||||
</NextHead>
|
|
||||||
</Fragment>
|
|
||||||
)
|
|
||||||
}
|
|
@ -1,251 +0,0 @@
|
|||||||
import { Fragment, useContext, useEffect, useRef, useState } from "react"
|
|
||||||
import { useRouter } from "next/router"
|
|
||||||
import { Event, getAllLocalStorageItems, getRefValue, getRefValues, isTrue, preventDefault, refs, spreadArraysOrObjects, uploadFiles, useEventLoop } from "/utils/state"
|
|
||||||
import { ColorModeContext, EventLoopContext, initialEvents, StateContext } from "/utils/context.js"
|
|
||||||
import "focus-visible/dist/focus-visible"
|
|
||||||
import { Box, Button, Heading, HStack, Image, Link, List, ListItem, Menu, MenuButton, MenuDivider, MenuItem, MenuList, Modal, ModalBody, ModalContent, ModalHeader, ModalOverlay, OrderedList, Spacer, Text, VStack, Wrap } from "@chakra-ui/react"
|
|
||||||
import { getEventURL } from "/utils/state.js"
|
|
||||||
import NextLink from "next/link"
|
|
||||||
import { HamburgerIcon } from "@chakra-ui/icons"
|
|
||||||
import NextHead from "next/head"
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
export default function Component() {
|
|
||||||
const state = useContext(StateContext)
|
|
||||||
const router = useRouter()
|
|
||||||
const [ colorMode, toggleColorMode ] = useContext(ColorModeContext)
|
|
||||||
const focusRef = useRef();
|
|
||||||
|
|
||||||
// Main event loop.
|
|
||||||
const [addEvents, connectError] = useContext(EventLoopContext)
|
|
||||||
|
|
||||||
// Set focus to the specified element.
|
|
||||||
useEffect(() => {
|
|
||||||
if (focusRef.current) {
|
|
||||||
focusRef.current.focus();
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
// Route after the initial page hydration.
|
|
||||||
useEffect(() => {
|
|
||||||
const change_complete = () => addEvents(initialEvents())
|
|
||||||
router.events.on('routeChangeComplete', change_complete)
|
|
||||||
return () => {
|
|
||||||
router.events.off('routeChangeComplete', change_complete)
|
|
||||||
}
|
|
||||||
}, [router])
|
|
||||||
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Fragment>
|
|
||||||
<Fragment>
|
|
||||||
{isTrue(connectError !== null) ? (
|
|
||||||
<Fragment>
|
|
||||||
<Modal isOpen={connectError !== null}>
|
|
||||||
<ModalOverlay>
|
|
||||||
<ModalContent>
|
|
||||||
<ModalHeader>
|
|
||||||
{`Connection Error`}
|
|
||||||
</ModalHeader>
|
|
||||||
<ModalBody>
|
|
||||||
<Text>
|
|
||||||
{`Cannot connect to server: `}
|
|
||||||
{(connectError !== null) ? connectError.message : ''}
|
|
||||||
{`. Check if server is reachable at `}
|
|
||||||
{getEventURL().href}
|
|
||||||
</Text>
|
|
||||||
</ModalBody>
|
|
||||||
</ModalContent>
|
|
||||||
</ModalOverlay>
|
|
||||||
</Modal>
|
|
||||||
</Fragment>
|
|
||||||
) : (
|
|
||||||
<Fragment/>
|
|
||||||
)}
|
|
||||||
</Fragment>
|
|
||||||
<HStack alignItems={`flex-start`} sx={{"transition": "left 0.5s, width 0.5s", "position": "relative"}}>
|
|
||||||
<Box sx={{"display": ["none", "none", "block"], "minWidth": "20em", "height": "100%", "position": "sticky", "top": "0px", "borderRight": "1px solid #F4F3F6"}}>
|
|
||||||
<VStack sx={{"height": "100dvh"}}>
|
|
||||||
<HStack sx={{"width": "100%", "borderBottom": "1px solid #F4F3F6", "padding": "1em"}}>
|
|
||||||
<Image src={`/icon.png`} sx={{"height": "4.5em"}}/>
|
|
||||||
<Spacer/>
|
|
||||||
</HStack>
|
|
||||||
<VStack alignItems={`flex-start`} sx={{"width": "100%", "overflowY": "auto", "padding": "1em"}}>
|
|
||||||
<Link as={NextLink} href={`/`} sx={{"width": "100%"}}>
|
|
||||||
<HStack sx={{"bg": isTrue((state.router.page.path === "/strona g\\u0142\\u00f3wna") || (((state.router.page.path === "/") && "Strona g\\u0142\\u00f3wna") === "Home")) ? `#fff0f7` : `transparent`, "color": isTrue((state.router.page.path === "/strona g\\u0142\\u00f3wna") || (((state.router.page.path === "/") && "Strona g\\u0142\\u00f3wna") === "Home")) ? `#1A1060` : `black`, "borderRadius": "0.375rem", "boxShadow": "0px 0px 0px 1px rgba(84, 82, 95, 0.14)", "width": "100%", "paddingX": "1em", "height": "2em"}}>
|
|
||||||
<Text>
|
|
||||||
{`Strona główna`}
|
|
||||||
</Text>
|
|
||||||
</HStack>
|
|
||||||
</Link>
|
|
||||||
<Link as={NextLink} href={`/dashboard`} sx={{"width": "100%"}}>
|
|
||||||
<HStack sx={{"bg": isTrue((state.router.page.path === "/dashboard") || (((state.router.page.path === "/") && "Dashboard") === "Home")) ? `#fff0f7` : `transparent`, "color": isTrue((state.router.page.path === "/dashboard") || (((state.router.page.path === "/") && "Dashboard") === "Home")) ? `#1A1060` : `black`, "borderRadius": "0.375rem", "boxShadow": "0px 0px 0px 1px rgba(84, 82, 95, 0.14)", "width": "100%", "paddingX": "1em", "height": "2em"}}>
|
|
||||||
<Text>
|
|
||||||
{`Dashboard`}
|
|
||||||
</Text>
|
|
||||||
</HStack>
|
|
||||||
</Link>
|
|
||||||
<Link as={NextLink} href={`/settings`} sx={{"width": "100%"}}>
|
|
||||||
<HStack sx={{"bg": isTrue((state.router.page.path === "/settings") || (((state.router.page.path === "/") && "Settings") === "Home")) ? `#fff0f7` : `transparent`, "color": isTrue((state.router.page.path === "/settings") || (((state.router.page.path === "/") && "Settings") === "Home")) ? `#1A1060` : `black`, "borderRadius": "0.375rem", "boxShadow": "0px 0px 0px 1px rgba(84, 82, 95, 0.14)", "width": "100%", "paddingX": "1em", "height": "2em"}}>
|
|
||||||
<Text>
|
|
||||||
{`Settings`}
|
|
||||||
</Text>
|
|
||||||
</HStack>
|
|
||||||
</Link>
|
|
||||||
</VStack>
|
|
||||||
<Spacer/>
|
|
||||||
</VStack>
|
|
||||||
</Box>
|
|
||||||
<Box sx={{"paddingTop": "5em", "paddingX": ["auto", "2em"], "flex": "1"}}>
|
|
||||||
<Box sx={{"alignItems": "flex-start", "boxShadow": "0px 0px 0px 1px rgba(84, 82, 95, 0.14)", "borderRadius": "0.375rem", "padding": "1em", "marginBottom": "2em"}}>
|
|
||||||
<VStack>
|
|
||||||
<Heading sx={{"fontSize": "3em", "marginBottom": "25px", "backgroundImage": "linear-gradient(271.68deg, #7566fe 0.75%, #f96caf 88.52%)", "backgroundClip": "text", "padding": "10px"}}>
|
|
||||||
{`Witaj w BlurMe!`}
|
|
||||||
</Heading>
|
|
||||||
<Wrap>
|
|
||||||
<HStack sx={{"align-self": "flex-start"}}>
|
|
||||||
<VStack sx={{"align-self": "flex-start"}}>
|
|
||||||
<Text sx={{"margin-left": "40px", "margin-top": "40px", "align-self": "flex-start", "margin-bottom": "25px", "fontSize": "1.2em"}}>
|
|
||||||
{`Aplikacja do anonimizacji zdjęć: usunięcie wybranych informacji identyfikujących ze zdjęć z wydarzeń i miejsc publicznych nigdy nie było tak proste!`}
|
|
||||||
</Text>
|
|
||||||
<Link as={NextLink} href={`settings`} sx={{"margin-top": "30px", "align": "center", "color": "rgba(117,102,254,255)", "button": true, "padding": "50xp", "fontSize": "1.2em"}}>
|
|
||||||
<Button>
|
|
||||||
{`Zacznij anonimizować`}
|
|
||||||
</Button>
|
|
||||||
</Link>
|
|
||||||
</VStack>
|
|
||||||
<Image src={`obraz1.png`} sx={{"margin-right": "60px", "height": "30em", "padding": "0.5em"}}/>
|
|
||||||
</HStack>
|
|
||||||
</Wrap>
|
|
||||||
<Text sx={{"height": "20px"}}>
|
|
||||||
{` `}
|
|
||||||
</Text>
|
|
||||||
<Text sx={{"fontSize": "1.5em", "fontWeight": "bold", "marginTop": "10px", "marginBottom": "10px"}}>
|
|
||||||
{`Dlaczego Blurme?`}
|
|
||||||
</Text>
|
|
||||||
<List sx={{"textAlign": "left"}}>
|
|
||||||
<ListItem>
|
|
||||||
<Text as={`b`}>
|
|
||||||
{`Prywatność: `}
|
|
||||||
</Text>
|
|
||||||
{`Nasza aplikacja zapewnia pełną ochronę prywatności, eliminując ryzyko identyfikacji osób niezwiązanych z anonimizacją.`}
|
|
||||||
</ListItem>
|
|
||||||
<ListItem>
|
|
||||||
<Text as={`b`}>
|
|
||||||
{`Prosta obsługa: `}
|
|
||||||
</Text>
|
|
||||||
{`Intuicyjny interfejs użytkownika sprawia, że korzystanie z Blurme jest łatwe dla każdego.`}
|
|
||||||
</ListItem>
|
|
||||||
<ListItem>
|
|
||||||
<Text as={`b`}>
|
|
||||||
{`Elastyczność: `}
|
|
||||||
</Text>
|
|
||||||
{`Wybierz, która twarz na zdjęciu ma pozostać niezmieniona, a my zadbamy o resztę. Indywidualizuj swoje zdjęcia zgodnie z własnymi potrzebami.`}
|
|
||||||
</ListItem>
|
|
||||||
</List>
|
|
||||||
<Text sx={{"fontSize": "1.5em", "fontWeight": "bold", "marginTop": "20px", "marginBottom": "10px"}}>
|
|
||||||
{`Jak to działa?`}
|
|
||||||
</Text>
|
|
||||||
<OrderedList>
|
|
||||||
<ListItem>
|
|
||||||
<Text as={`b`}>
|
|
||||||
{`Wgraj Zdjęcie: `}
|
|
||||||
</Text>
|
|
||||||
{`Prosty proces rozpoczyna się od dodania zdjęcia, które chcesz anonimizować.`}
|
|
||||||
</ListItem>
|
|
||||||
<ListItem>
|
|
||||||
<Text as={`b`}>
|
|
||||||
{`Zaznacz Twarz do Ochrony: `}
|
|
||||||
</Text>
|
|
||||||
{`Oznacz twarz, którą chcesz zachować.`}
|
|
||||||
</ListItem>
|
|
||||||
<ListItem>
|
|
||||||
<Text as={`b`}>
|
|
||||||
{`Anonimizuj Resztę: `}
|
|
||||||
</Text>
|
|
||||||
{`Naciśnij przycisk, a Blurme automatycznie zastosuje efekt rozmycia do wszystkich obszarów poza zaznaczoną twarzą.`}
|
|
||||||
</ListItem>
|
|
||||||
<ListItem>
|
|
||||||
<Text as={`b`}>
|
|
||||||
{`Pobierz i Podziel Się: `}
|
|
||||||
</Text>
|
|
||||||
{`Gotowe! Pobierz anonimizowane zdjęcie i dziel się nim bez obaw o prywatność.`}
|
|
||||||
</ListItem>
|
|
||||||
</OrderedList>
|
|
||||||
<Text sx={{"fontSize": "1.5em", "fontWeight": "bold", "marginTop": "20px", "marginBottom": "10px"}}>
|
|
||||||
{`Do Czego?`}
|
|
||||||
</Text>
|
|
||||||
<List>
|
|
||||||
<ListItem sx={{"textAlign": "left"}}>
|
|
||||||
<Text as={`b`}>
|
|
||||||
{`Ochrona Tożsamości: `}
|
|
||||||
</Text>
|
|
||||||
{`Idealne do usuwania twarzy nieznajomych lub przypadkowych przechodniów z tła.`}
|
|
||||||
</ListItem>
|
|
||||||
<ListItem sx={{"textAlign": "left"}}>
|
|
||||||
<Text as={`b`}>
|
|
||||||
{`Zachowanie Prywatności: `}
|
|
||||||
</Text>
|
|
||||||
{`Przydatne w sytuacjach, gdzie chcesz udostępnić zdjęcia publicznie, ale z zachowaniem prywatności pewnych osób.`}
|
|
||||||
</ListItem>
|
|
||||||
<ListItem sx={{"textAlign": "left"}}>
|
|
||||||
<Text as={`b`}>
|
|
||||||
{`Kreatywność: `}
|
|
||||||
</Text>
|
|
||||||
{`Wyraź swoją kreatywność, zachowując jednocześnie istotne elementy na zdjęciach.`}
|
|
||||||
</ListItem>
|
|
||||||
</List>
|
|
||||||
<Text sx={{"textAlign": "left"}}>
|
|
||||||
{`Blurme to więcej niż tylko narzędzie - to rozwiązanie, które pozwala Ci kontrolować, jak prezentujesz swoje zdjęcia online, zachowując jednocześnie pełną prywatność. Przekształć swoje obrazy już teraz!`}
|
|
||||||
</Text>
|
|
||||||
</VStack>
|
|
||||||
</Box>
|
|
||||||
</Box>
|
|
||||||
<Box sx={{"position": "fixed", "right": "1.5em", "top": "1.5em", "zIndex": "500"}}>
|
|
||||||
<Menu>
|
|
||||||
<MenuButton sx={{"width": "3em", "height": "3em", "backgroundColor": "white", "border": "1px solid #F4F3F6", "borderRadius": "0.375rem"}}>
|
|
||||||
<HamburgerIcon sx={{"size": "4em", "color": "black"}}/>
|
|
||||||
</MenuButton>
|
|
||||||
<MenuList>
|
|
||||||
<MenuItem sx={{"_hover": {"bg": "#fff0f7"}}}>
|
|
||||||
<Link as={NextLink} href={`/`} sx={{"width": "100%"}}>
|
|
||||||
{`Strona główna`}
|
|
||||||
</Link>
|
|
||||||
</MenuItem>
|
|
||||||
<MenuItem sx={{"_hover": {"bg": "#fff0f7"}}}>
|
|
||||||
<Link as={NextLink} href={`/dashboard`} sx={{"width": "100%"}}>
|
|
||||||
{`Dashboard`}
|
|
||||||
</Link>
|
|
||||||
</MenuItem>
|
|
||||||
<MenuItem sx={{"_hover": {"bg": "#fff0f7"}}}>
|
|
||||||
<Link as={NextLink} href={`/settings`} sx={{"width": "100%"}}>
|
|
||||||
{`Settings`}
|
|
||||||
</Link>
|
|
||||||
</MenuItem>
|
|
||||||
<MenuDivider/>
|
|
||||||
<MenuItem sx={{"_hover": {"bg": "#fff0f7"}}}>
|
|
||||||
<Link as={NextLink} href={`https://github.com/reflex-dev`} sx={{"width": "100%"}}>
|
|
||||||
{`About`}
|
|
||||||
</Link>
|
|
||||||
</MenuItem>
|
|
||||||
<MenuItem sx={{"_hover": {"bg": "#fff0f7"}}}>
|
|
||||||
<Link as={NextLink} href={`mailto:founders@=reflex.dev`} sx={{"width": "100%"}}>
|
|
||||||
{`Contact`}
|
|
||||||
</Link>
|
|
||||||
</MenuItem>
|
|
||||||
</MenuList>
|
|
||||||
</Menu>
|
|
||||||
</Box>
|
|
||||||
</HStack>
|
|
||||||
<NextHead>
|
|
||||||
<title>
|
|
||||||
{`Strona główna`}
|
|
||||||
</title>
|
|
||||||
<meta content={`A Reflex app.`} name={`description`}/>
|
|
||||||
<meta content={`/github.svg`} property={`og:image`}/>
|
|
||||||
<meta content={`width=device-width, shrink-to-fit=no, initial-scale=1`} name={`viewport`}/>
|
|
||||||
</NextHead>
|
|
||||||
</Fragment>
|
|
||||||
)
|
|
||||||
}
|
|
@ -1,170 +0,0 @@
|
|||||||
import { Fragment, useContext, useEffect, useRef, useState } from "react"
|
|
||||||
import { useRouter } from "next/router"
|
|
||||||
import { Event, getAllLocalStorageItems, getRefValue, getRefValues, isTrue, preventDefault, refs, set_val, spreadArraysOrObjects, uploadFiles, useEventLoop } from "/utils/state"
|
|
||||||
import { ColorModeContext, EventLoopContext, initialEvents, StateContext } from "/utils/context.js"
|
|
||||||
import "focus-visible/dist/focus-visible"
|
|
||||||
import { Box, Heading, HStack, Image, Input, Link, Menu, MenuButton, MenuDivider, MenuItem, MenuList, Modal, ModalBody, ModalContent, ModalHeader, ModalOverlay, Spacer, Text, VStack } from "@chakra-ui/react"
|
|
||||||
import { getEventURL } from "/utils/state.js"
|
|
||||||
import NextLink from "next/link"
|
|
||||||
import ReactDropzone from "react-dropzone"
|
|
||||||
import { HamburgerIcon } from "@chakra-ui/icons"
|
|
||||||
import NextHead from "next/head"
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
export default function Component() {
|
|
||||||
const state = useContext(StateContext)
|
|
||||||
const router = useRouter()
|
|
||||||
const [ colorMode, toggleColorMode ] = useContext(ColorModeContext)
|
|
||||||
const focusRef = useRef();
|
|
||||||
|
|
||||||
// Main event loop.
|
|
||||||
const [addEvents, connectError] = useContext(EventLoopContext)
|
|
||||||
|
|
||||||
// Set focus to the specified element.
|
|
||||||
useEffect(() => {
|
|
||||||
if (focusRef.current) {
|
|
||||||
focusRef.current.focus();
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
// Route after the initial page hydration.
|
|
||||||
useEffect(() => {
|
|
||||||
const change_complete = () => addEvents(initialEvents())
|
|
||||||
router.events.on('routeChangeComplete', change_complete)
|
|
||||||
return () => {
|
|
||||||
router.events.off('routeChangeComplete', change_complete)
|
|
||||||
}
|
|
||||||
}, [router])
|
|
||||||
|
|
||||||
const [files, setFiles] = useState([]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Fragment>
|
|
||||||
<Fragment>
|
|
||||||
{isTrue(connectError !== null) ? (
|
|
||||||
<Fragment>
|
|
||||||
<Modal isOpen={connectError !== null}>
|
|
||||||
<ModalOverlay>
|
|
||||||
<ModalContent>
|
|
||||||
<ModalHeader>
|
|
||||||
{`Connection Error`}
|
|
||||||
</ModalHeader>
|
|
||||||
<ModalBody>
|
|
||||||
<Text>
|
|
||||||
{`Cannot connect to server: `}
|
|
||||||
{(connectError !== null) ? connectError.message : ''}
|
|
||||||
{`. Check if server is reachable at `}
|
|
||||||
{getEventURL().href}
|
|
||||||
</Text>
|
|
||||||
</ModalBody>
|
|
||||||
</ModalContent>
|
|
||||||
</ModalOverlay>
|
|
||||||
</Modal>
|
|
||||||
</Fragment>
|
|
||||||
) : (
|
|
||||||
<Fragment/>
|
|
||||||
)}
|
|
||||||
</Fragment>
|
|
||||||
<HStack alignItems={`flex-start`} sx={{"transition": "left 0.5s, width 0.5s", "position": "relative"}}>
|
|
||||||
<Box sx={{"display": ["none", "none", "block"], "minWidth": "20em", "height": "100%", "position": "sticky", "top": "0px", "borderRight": "1px solid #F4F3F6"}}>
|
|
||||||
<VStack sx={{"height": "100dvh"}}>
|
|
||||||
<HStack sx={{"width": "100%", "borderBottom": "1px solid #F4F3F6", "padding": "1em"}}>
|
|
||||||
<Image src={`/icon.png`} sx={{"height": "4.5em"}}/>
|
|
||||||
<Spacer/>
|
|
||||||
</HStack>
|
|
||||||
<VStack alignItems={`flex-start`} sx={{"width": "100%", "overflowY": "auto", "padding": "1em"}}>
|
|
||||||
<Link as={NextLink} href={`/`} sx={{"width": "100%"}}>
|
|
||||||
<HStack sx={{"bg": isTrue((state.router.page.path === "/strona g\\u0142\\u00f3wna") || (((state.router.page.path === "/") && "Strona g\\u0142\\u00f3wna") === "Home")) ? `#fff0f7` : `transparent`, "color": isTrue((state.router.page.path === "/strona g\\u0142\\u00f3wna") || (((state.router.page.path === "/") && "Strona g\\u0142\\u00f3wna") === "Home")) ? `#1A1060` : `black`, "borderRadius": "0.375rem", "boxShadow": "0px 0px 0px 1px rgba(84, 82, 95, 0.14)", "width": "100%", "paddingX": "1em", "height": "2em"}}>
|
|
||||||
<Text>
|
|
||||||
{`Strona główna`}
|
|
||||||
</Text>
|
|
||||||
</HStack>
|
|
||||||
</Link>
|
|
||||||
<Link as={NextLink} href={`/dashboard`} sx={{"width": "100%"}}>
|
|
||||||
<HStack sx={{"bg": isTrue((state.router.page.path === "/dashboard") || (((state.router.page.path === "/") && "Dashboard") === "Home")) ? `#fff0f7` : `transparent`, "color": isTrue((state.router.page.path === "/dashboard") || (((state.router.page.path === "/") && "Dashboard") === "Home")) ? `#1A1060` : `black`, "borderRadius": "0.375rem", "boxShadow": "0px 0px 0px 1px rgba(84, 82, 95, 0.14)", "width": "100%", "paddingX": "1em", "height": "2em"}}>
|
|
||||||
<Text>
|
|
||||||
{`Dashboard`}
|
|
||||||
</Text>
|
|
||||||
</HStack>
|
|
||||||
</Link>
|
|
||||||
<Link as={NextLink} href={`/settings`} sx={{"width": "100%"}}>
|
|
||||||
<HStack sx={{"bg": isTrue((state.router.page.path === "/settings") || (((state.router.page.path === "/") && "Settings") === "Home")) ? `#fff0f7` : `transparent`, "color": isTrue((state.router.page.path === "/settings") || (((state.router.page.path === "/") && "Settings") === "Home")) ? `#1A1060` : `black`, "borderRadius": "0.375rem", "boxShadow": "0px 0px 0px 1px rgba(84, 82, 95, 0.14)", "width": "100%", "paddingX": "1em", "height": "2em"}}>
|
|
||||||
<Text>
|
|
||||||
{`Settings`}
|
|
||||||
</Text>
|
|
||||||
</HStack>
|
|
||||||
</Link>
|
|
||||||
</VStack>
|
|
||||||
<Spacer/>
|
|
||||||
</VStack>
|
|
||||||
</Box>
|
|
||||||
<Box sx={{"paddingTop": "5em", "paddingX": ["auto", "2em"], "flex": "1"}}>
|
|
||||||
<Box sx={{"alignItems": "flex-start", "boxShadow": "0px 0px 0px 1px rgba(84, 82, 95, 0.14)", "borderRadius": "0.375rem", "padding": "1em", "marginBottom": "2em"}}>
|
|
||||||
<VStack>
|
|
||||||
<Heading sx={{"fontSize": "3em", "marginBottom": "25px", "backgroundImage": "linear-gradient(271.68deg, #7566fe 0.75%, #f96caf 88.52%)", "backgroundClip": "text", "padding": "10px"}}>
|
|
||||||
{`BlurMe`}
|
|
||||||
</Heading>
|
|
||||||
<Text sx={{"fontSize": "1.2em"}}>
|
|
||||||
{`Dodaj zdjęcie, które chcesz zanonimizować`}
|
|
||||||
</Text>
|
|
||||||
<ReactDropzone multiple={true} onDrop={e => setFiles((files) => e)}>
|
|
||||||
{({ getRootProps, getInputProps }) => (
|
|
||||||
<Box sx={{"border": "1px dotted rgb(107,99,246)", "padding": "5em"}} {...getRootProps()}>
|
|
||||||
<Input type={`file`} {...getInputProps()}/>
|
|
||||||
<Text>
|
|
||||||
{`Drag and drop files here or click to select files`}
|
|
||||||
</Text>
|
|
||||||
</Box>
|
|
||||||
)}
|
|
||||||
</ReactDropzone>
|
|
||||||
</VStack>
|
|
||||||
</Box>
|
|
||||||
</Box>
|
|
||||||
<Box sx={{"position": "fixed", "right": "1.5em", "top": "1.5em", "zIndex": "500"}}>
|
|
||||||
<Menu>
|
|
||||||
<MenuButton sx={{"width": "3em", "height": "3em", "backgroundColor": "white", "border": "1px solid #F4F3F6", "borderRadius": "0.375rem"}}>
|
|
||||||
<HamburgerIcon sx={{"size": "4em", "color": "black"}}/>
|
|
||||||
</MenuButton>
|
|
||||||
<MenuList>
|
|
||||||
<MenuItem sx={{"_hover": {"bg": "#fff0f7"}}}>
|
|
||||||
<Link as={NextLink} href={`/`} sx={{"width": "100%"}}>
|
|
||||||
{`Strona główna`}
|
|
||||||
</Link>
|
|
||||||
</MenuItem>
|
|
||||||
<MenuItem sx={{"_hover": {"bg": "#fff0f7"}}}>
|
|
||||||
<Link as={NextLink} href={`/dashboard`} sx={{"width": "100%"}}>
|
|
||||||
{`Dashboard`}
|
|
||||||
</Link>
|
|
||||||
</MenuItem>
|
|
||||||
<MenuItem sx={{"_hover": {"bg": "#fff0f7"}}}>
|
|
||||||
<Link as={NextLink} href={`/settings`} sx={{"width": "100%"}}>
|
|
||||||
{`Settings`}
|
|
||||||
</Link>
|
|
||||||
</MenuItem>
|
|
||||||
<MenuDivider/>
|
|
||||||
<MenuItem sx={{"_hover": {"bg": "#fff0f7"}}}>
|
|
||||||
<Link as={NextLink} href={`https://github.com/reflex-dev`} sx={{"width": "100%"}}>
|
|
||||||
{`About`}
|
|
||||||
</Link>
|
|
||||||
</MenuItem>
|
|
||||||
<MenuItem sx={{"_hover": {"bg": "#fff0f7"}}}>
|
|
||||||
<Link as={NextLink} href={`mailto:founders@=reflex.dev`} sx={{"width": "100%"}}>
|
|
||||||
{`Contact`}
|
|
||||||
</Link>
|
|
||||||
</MenuItem>
|
|
||||||
</MenuList>
|
|
||||||
</Menu>
|
|
||||||
</Box>
|
|
||||||
</HStack>
|
|
||||||
<NextHead>
|
|
||||||
<title>
|
|
||||||
{`Settings`}
|
|
||||||
</title>
|
|
||||||
<meta content={`A Reflex app.`} name={`description`}/>
|
|
||||||
<meta content={`favicon.ico`} property={`og:image`}/>
|
|
||||||
<meta content={`width=device-width, shrink-to-fit=no, initial-scale=1`} name={`viewport`}/>
|
|
||||||
</NextHead>
|
|
||||||
</Fragment>
|
|
||||||
)
|
|
||||||
}
|
|
@ -1,6 +0,0 @@
|
|||||||
module.exports = {
|
|
||||||
plugins: {
|
|
||||||
tailwindcss: {},
|
|
||||||
autoprefixer: {},
|
|
||||||
},
|
|
||||||
}
|
|
Binary file not shown.
Before Width: | Height: | Size: 11 KiB |
@ -1,10 +0,0 @@
|
|||||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
||||||
<g id="Github" clip-path="url(#clip0_469_1929)">
|
|
||||||
<path id="Vector" d="M8.0004 0.587524C3.80139 0.587524 0.400391 3.98851 0.400391 8.1875C0.400391 11.5505 2.57589 14.391 5.59689 15.398C5.97689 15.4645 6.11939 15.2365 6.11939 15.037C6.11939 14.8565 6.10989 14.258 6.10989 13.6215C4.20039 13.973 3.70639 13.156 3.55439 12.7285C3.46889 12.51 3.09839 11.8355 2.77539 11.655C2.50939 11.5125 2.12939 11.161 2.76589 11.1515C3.36439 11.142 3.79189 11.7025 3.93439 11.9305C4.61839 13.08 5.71089 12.757 6.14789 12.5575C6.21439 12.0635 6.41388 11.731 6.6324 11.541C4.94139 11.351 3.17439 10.6955 3.17439 7.7885C3.17439 6.962 3.46889 6.27801 3.95339 5.74601C3.87739 5.55601 3.61139 4.77701 4.02939 3.73201C4.02939 3.73201 4.66589 3.53251 6.11939 4.51101C6.7274 4.34001 7.3734 4.25451 8.0194 4.25451C8.6654 4.25451 9.3114 4.34001 9.9194 4.51101C11.3729 3.52301 12.0094 3.73201 12.0094 3.73201C12.4274 4.77701 12.1614 5.55601 12.0854 5.74601C12.5699 6.27801 12.8644 6.9525 12.8644 7.7885C12.8644 10.705 11.0879 11.351 9.3969 11.541C9.6724 11.7785 9.9099 12.2345 9.9099 12.947C9.9099 13.9635 9.9004 14.7805 9.9004 15.037C9.9004 15.2365 10.0429 15.474 10.4229 15.398C13.5165 14.3536 15.5996 11.4527 15.6004 8.1875C15.6004 3.98851 12.1994 0.587524 8.0004 0.587524Z" fill="#494369"/>
|
|
||||||
</g>
|
|
||||||
<defs>
|
|
||||||
<clipPath id="clip0_469_1929">
|
|
||||||
<rect width="16" height="16" fill="white"/>
|
|
||||||
</clipPath>
|
|
||||||
</defs>
|
|
||||||
</svg>
|
|
Before Width: | Height: | Size: 1.4 KiB |
Binary file not shown.
Before Width: | Height: | Size: 95 KiB |
Binary file not shown.
Before Width: | Height: | Size: 93 KiB |
@ -1,13 +0,0 @@
|
|||||||
<svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
||||||
<g id="PaneLeft" clip-path="url(#clip0_469_1942)">
|
|
||||||
<g id="Vector">
|
|
||||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M7.80217 0.525009C7.34654 0.525009 6.97717 0.894373 6.97717 1.35001V10.65C6.97717 11.1056 7.34654 11.475 7.80217 11.475H10.6522C11.1078 11.475 11.4772 11.1056 11.4772 10.65V1.35001C11.4772 0.894373 11.1078 0.525009 10.6522 0.525009H7.80217ZM8.02717 10.425V1.57501H10.4272V10.425H8.02717Z" fill="#494369"/>
|
|
||||||
<path d="M3.78215 8.14502L2.16213 6.525H5.92717V5.475H2.16213L3.78215 3.85498L3.03969 3.11252L0.523438 5.62877V6.37123L3.03969 8.88748L3.78215 8.14502Z" fill="#494369"/>
|
|
||||||
</g>
|
|
||||||
</g>
|
|
||||||
<defs>
|
|
||||||
<clipPath id="clip0_469_1942">
|
|
||||||
<rect width="12" height="12" fill="white"/>
|
|
||||||
</clipPath>
|
|
||||||
</defs>
|
|
||||||
</svg>
|
|
Before Width: | Height: | Size: 807 B |
@ -1 +0,0 @@
|
|||||||
{"version": "0.3.2", "project_hash": 99276045562630266666819420787377202120}
|
|
File diff suppressed because it is too large
Load Diff
@ -1 +0,0 @@
|
|||||||
@import url('./tailwind.css');
|
|
@ -1,3 +0,0 @@
|
|||||||
@tailwind base;
|
|
||||||
@tailwind components;
|
|
||||||
@tailwind utilities;
|
|
@ -1,7 +0,0 @@
|
|||||||
/** @type {import('tailwindcss').Config} */
|
|
||||||
module.exports = {
|
|
||||||
content: ["./pages/**/*.{js,ts,jsx,tsx}"],
|
|
||||||
theme: null,
|
|
||||||
plugins: [
|
|
||||||
],
|
|
||||||
};
|
|
@ -1,36 +0,0 @@
|
|||||||
import { useEffect, useRef, useState } from "react";
|
|
||||||
import { useRouter } from "next/router";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* React hook for use in /404 page to enable client-side routing.
|
|
||||||
*
|
|
||||||
* Uses the next/router to redirect to the provided URL when loading
|
|
||||||
* the 404 page (for example as a fallback in static hosting situations).
|
|
||||||
*
|
|
||||||
* @returns {boolean} routeNotFound - true if the current route is an actual 404
|
|
||||||
*/
|
|
||||||
export const useClientSideRouting = () => {
|
|
||||||
const [routeNotFound, setRouteNotFound] = useState(false)
|
|
||||||
const didRedirect = useRef(false)
|
|
||||||
const router = useRouter()
|
|
||||||
useEffect(() => {
|
|
||||||
if (
|
|
||||||
router.isReady &&
|
|
||||||
!didRedirect.current // have not tried redirecting yet
|
|
||||||
) {
|
|
||||||
didRedirect.current = true // never redirect twice to avoid "Hard Navigate" error
|
|
||||||
// attempt to redirect to the route in the browser address bar once
|
|
||||||
router.replace({
|
|
||||||
pathname: window.location.pathname,
|
|
||||||
query: window.location.search.slice(1),
|
|
||||||
})
|
|
||||||
.catch((e) => {
|
|
||||||
setRouteNotFound(true) // navigation failed, so this is a real 404
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}, [router.isReady]);
|
|
||||||
|
|
||||||
// Return the reactive bool, to avoid flashing 404 page until we know for sure
|
|
||||||
// the route is not found.
|
|
||||||
return routeNotFound
|
|
||||||
}
|
|
@ -1,5 +0,0 @@
|
|||||||
import { memo } from "react"
|
|
||||||
import { E, isTrue } from "/utils/state"
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1,30 +0,0 @@
|
|||||||
import { createContext, useState } from "react"
|
|
||||||
import { Event, hydrateClientStorage, useEventLoop } from "/utils/state.js"
|
|
||||||
|
|
||||||
export const initialState = {"is_hydrated": false, "router": {"session": {"client_token": "", "client_ip": "", "session_id": ""}, "headers": {"host": "", "origin": "", "upgrade": "", "connection": "", "pragma": "", "cache_control": "", "user_agent": "", "sec_websocket_version": "", "sec_websocket_key": "", "sec_websocket_extensions": "", "accept_encoding": "", "accept_language": ""}, "page": {"host": "", "path": "", "raw_path": "", "full_path": "", "full_raw_path": "", "params": {}}}}
|
|
||||||
|
|
||||||
export const ColorModeContext = createContext(null);
|
|
||||||
export const StateContext = createContext(null);
|
|
||||||
export const EventLoopContext = createContext(null);
|
|
||||||
export const clientStorage = {"cookies": {}, "local_storage": {}}
|
|
||||||
|
|
||||||
export const initialEvents = () => [
|
|
||||||
Event('state.hydrate', hydrateClientStorage(clientStorage)),
|
|
||||||
]
|
|
||||||
|
|
||||||
export const isDevMode = true
|
|
||||||
|
|
||||||
export function EventLoopProvider({ children }) {
|
|
||||||
const [state, addEvents, connectError] = useEventLoop(
|
|
||||||
initialState,
|
|
||||||
initialEvents,
|
|
||||||
clientStorage,
|
|
||||||
)
|
|
||||||
return (
|
|
||||||
<EventLoopContext.Provider value={[addEvents, connectError]}>
|
|
||||||
<StateContext.Provider value={state}>
|
|
||||||
{children}
|
|
||||||
</StateContext.Provider>
|
|
||||||
</EventLoopContext.Provider>
|
|
||||||
)
|
|
||||||
}
|
|
@ -1,67 +0,0 @@
|
|||||||
import { GridCellKind } from "@glideapps/glide-data-grid"
|
|
||||||
|
|
||||||
export function getDEColumn(columns, col) {
|
|
||||||
let c = columns[col];
|
|
||||||
c.pos = col;
|
|
||||||
return c;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getDERow(data, row) {
|
|
||||||
return data[row];
|
|
||||||
}
|
|
||||||
|
|
||||||
export function locateCell(row, column) {
|
|
||||||
if (Array.isArray(row)) {
|
|
||||||
return row[column.pos];
|
|
||||||
} else {
|
|
||||||
return row[column.id];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function formatCell(value, column) {
|
|
||||||
const editable = column.editable || true
|
|
||||||
switch (column.type) {
|
|
||||||
case "int":
|
|
||||||
case "float":
|
|
||||||
return {
|
|
||||||
kind: GridCellKind.Number,
|
|
||||||
data: value,
|
|
||||||
displayData: value + "",
|
|
||||||
readonly: !editable,
|
|
||||||
allowOverlay: editable,
|
|
||||||
}
|
|
||||||
case "datetime":
|
|
||||||
// value = moment format?
|
|
||||||
case "str":
|
|
||||||
return {
|
|
||||||
kind: GridCellKind.Text,
|
|
||||||
data: value,
|
|
||||||
displayData: value,
|
|
||||||
readonly: !editable,
|
|
||||||
allowOverlay: editable,
|
|
||||||
}
|
|
||||||
case "bool":
|
|
||||||
return {
|
|
||||||
kind: GridCellKind.Boolean,
|
|
||||||
data: value,
|
|
||||||
readonly: !editable,
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
console.log("Warning: column.type is undefined for column.title=" + column.title)
|
|
||||||
return {
|
|
||||||
kind: GridCellKind.Text,
|
|
||||||
data: value,
|
|
||||||
displayData: column.type
|
|
||||||
}
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export function formatDataEditorCells(col, row, columns, data) {
|
|
||||||
if (row < data.length && col < columns.length) {
|
|
||||||
const column = getDEColumn(columns, col);
|
|
||||||
const rowData = getDERow(data, row);
|
|
||||||
const cellData = locateCell(rowData, column);
|
|
||||||
return formatCell(cellData, column);
|
|
||||||
}
|
|
||||||
return { kind: GridCellKind.Loading };
|
|
||||||
}
|
|
@ -1,589 +0,0 @@
|
|||||||
// State management for Reflex web apps.
|
|
||||||
import axios from "axios";
|
|
||||||
import io from "socket.io-client";
|
|
||||||
import JSON5 from "json5";
|
|
||||||
import env from "env.json";
|
|
||||||
import Cookies from "universal-cookie";
|
|
||||||
import { useEffect, useReducer, useRef, useState } from "react";
|
|
||||||
import Router, { useRouter } from "next/router";
|
|
||||||
import { initialEvents } from "utils/context.js"
|
|
||||||
|
|
||||||
// Endpoint URLs.
|
|
||||||
const EVENTURL = env.EVENT
|
|
||||||
const UPLOADURL = env.UPLOAD
|
|
||||||
|
|
||||||
// Global variable to hold the token.
|
|
||||||
let token;
|
|
||||||
|
|
||||||
// Key for the token in the session storage.
|
|
||||||
const TOKEN_KEY = "token";
|
|
||||||
|
|
||||||
// create cookie instance
|
|
||||||
const cookies = new Cookies();
|
|
||||||
|
|
||||||
// Dictionary holding component references.
|
|
||||||
export const refs = {};
|
|
||||||
|
|
||||||
// Flag ensures that only one event is processing on the backend concurrently.
|
|
||||||
let event_processing = false
|
|
||||||
// Array holding pending events to be processed.
|
|
||||||
const event_queue = [];
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Generate a UUID (Used for session tokens).
|
|
||||||
* Taken from: https://stackoverflow.com/questions/105034/how-do-i-create-a-guid-uuid
|
|
||||||
* @returns A UUID.
|
|
||||||
*/
|
|
||||||
const generateUUID = () => {
|
|
||||||
let d = new Date().getTime(),
|
|
||||||
d2 = (performance && performance.now && performance.now() * 1000) || 0;
|
|
||||||
return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => {
|
|
||||||
let r = Math.random() * 16;
|
|
||||||
if (d > 0) {
|
|
||||||
r = (d + r) % 16 | 0;
|
|
||||||
d = Math.floor(d / 16);
|
|
||||||
} else {
|
|
||||||
r = (d2 + r) % 16 | 0;
|
|
||||||
d2 = Math.floor(d2 / 16);
|
|
||||||
}
|
|
||||||
return (c == "x" ? r : (r & 0x7) | 0x8).toString(16);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the token for the current session.
|
|
||||||
* @returns The token.
|
|
||||||
*/
|
|
||||||
export const getToken = () => {
|
|
||||||
if (token) {
|
|
||||||
return token;
|
|
||||||
}
|
|
||||||
if (window) {
|
|
||||||
if (!window.sessionStorage.getItem(TOKEN_KEY)) {
|
|
||||||
window.sessionStorage.setItem(TOKEN_KEY, generateUUID());
|
|
||||||
}
|
|
||||||
token = window.sessionStorage.getItem(TOKEN_KEY);
|
|
||||||
}
|
|
||||||
return token;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the URL for the websocket connection
|
|
||||||
* @returns The websocket URL object.
|
|
||||||
*/
|
|
||||||
export const getEventURL = () => {
|
|
||||||
// Get backend URL object from the endpoint.
|
|
||||||
const endpoint = new URL(EVENTURL);
|
|
||||||
if (endpoint.hostname === "localhost") {
|
|
||||||
// If the backend URL references localhost, and the frontend is not on localhost,
|
|
||||||
// then use the frontend host.
|
|
||||||
const frontend_hostname = window.location.hostname;
|
|
||||||
if (frontend_hostname !== "localhost") {
|
|
||||||
endpoint.hostname = frontend_hostname;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return endpoint
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Apply a delta to the state.
|
|
||||||
* @param state The state to apply the delta to.
|
|
||||||
* @param delta The delta to apply.
|
|
||||||
*/
|
|
||||||
export const applyDelta = (state, delta) => {
|
|
||||||
const new_state = { ...state }
|
|
||||||
for (const substate in delta) {
|
|
||||||
let s = new_state;
|
|
||||||
const path = substate.split(".").slice(1);
|
|
||||||
while (path.length > 0) {
|
|
||||||
s = s[path.shift()];
|
|
||||||
}
|
|
||||||
for (const key in delta[substate]) {
|
|
||||||
s[key] = delta[substate][key];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return new_state
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get all local storage items in a key-value object.
|
|
||||||
* @returns object of items in local storage.
|
|
||||||
*/
|
|
||||||
export const getAllLocalStorageItems = () => {
|
|
||||||
var localStorageItems = {};
|
|
||||||
|
|
||||||
for (var i = 0, len = localStorage.length; i < len; i++) {
|
|
||||||
var key = localStorage.key(i);
|
|
||||||
localStorageItems[key] = localStorage.getItem(key);
|
|
||||||
}
|
|
||||||
|
|
||||||
return localStorageItems;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handle frontend event or send the event to the backend via Websocket.
|
|
||||||
* @param event The event to send.
|
|
||||||
* @param socket The socket object to send the event on.
|
|
||||||
*
|
|
||||||
* @returns True if the event was sent, false if it was handled locally.
|
|
||||||
*/
|
|
||||||
export const applyEvent = async (event, socket) => {
|
|
||||||
// Handle special events
|
|
||||||
if (event.name == "_redirect") {
|
|
||||||
if (event.payload.external)
|
|
||||||
window.open(event.payload.path, "_blank");
|
|
||||||
else
|
|
||||||
Router.push(event.payload.path);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (event.name == "_console") {
|
|
||||||
console.log(event.payload.message);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (event.name == "_remove_cookie") {
|
|
||||||
cookies.remove(event.payload.key, { ...event.payload.options })
|
|
||||||
queueEvents(initialEvents(), socket)
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (event.name == "_clear_local_storage") {
|
|
||||||
localStorage.clear();
|
|
||||||
queueEvents(initialEvents(), socket)
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (event.name == "_remove_local_storage") {
|
|
||||||
localStorage.removeItem(event.payload.key);
|
|
||||||
queueEvents(initialEvents(), socket)
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (event.name == "_set_clipboard") {
|
|
||||||
const content = event.payload.content;
|
|
||||||
navigator.clipboard.writeText(content);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (event.name == "_download") {
|
|
||||||
const a = document.createElement('a');
|
|
||||||
a.hidden = true;
|
|
||||||
a.href = event.payload.url;
|
|
||||||
if (event.payload.filename)
|
|
||||||
a.download = event.payload.filename;
|
|
||||||
a.click();
|
|
||||||
a.remove();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (event.name == "_alert") {
|
|
||||||
alert(event.payload.message);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (event.name == "_set_focus") {
|
|
||||||
const ref =
|
|
||||||
event.payload.ref in refs ? refs[event.payload.ref] : event.payload.ref;
|
|
||||||
ref.current.focus();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (event.name == "_set_value") {
|
|
||||||
const ref =
|
|
||||||
event.payload.ref in refs ? refs[event.payload.ref] : event.payload.ref;
|
|
||||||
ref.current.value = event.payload.value;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (event.name == "_call_script") {
|
|
||||||
try {
|
|
||||||
const eval_result = eval(event.payload.javascript_code);
|
|
||||||
if (event.payload.callback) {
|
|
||||||
if (!!eval_result && typeof eval_result.then === 'function') {
|
|
||||||
eval(event.payload.callback)(await eval_result)
|
|
||||||
} else {
|
|
||||||
eval(event.payload.callback)(eval_result)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
console.log("_call_script", e);
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update token and router data (if missing).
|
|
||||||
event.token = getToken()
|
|
||||||
if (event.router_data === undefined || Object.keys(event.router_data).length === 0) {
|
|
||||||
event.router_data = (({ pathname, query, asPath }) => ({ pathname, query, asPath }))(Router)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Send the event to the server.
|
|
||||||
if (socket) {
|
|
||||||
socket.emit("event", JSON.stringify(event, (k, v) => v === undefined ? null : v));
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Send an event to the server via REST.
|
|
||||||
* @param event The current event.
|
|
||||||
* @param state The state with the event queue.
|
|
||||||
*
|
|
||||||
* @returns Whether the event was sent.
|
|
||||||
*/
|
|
||||||
export const applyRestEvent = async (event) => {
|
|
||||||
let eventSent = false;
|
|
||||||
if (event.handler == "uploadFiles") {
|
|
||||||
eventSent = await uploadFiles(event.name, event.payload.files);
|
|
||||||
}
|
|
||||||
return eventSent;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Queue events to be processed and trigger processing of queue.
|
|
||||||
* @param events Array of events to queue.
|
|
||||||
* @param socket The socket object to send the event on.
|
|
||||||
*/
|
|
||||||
export const queueEvents = async (events, socket) => {
|
|
||||||
event_queue.push(...events)
|
|
||||||
await processEvent(socket.current)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Process an event off the event queue.
|
|
||||||
* @param socket The socket object to send the event on.
|
|
||||||
*/
|
|
||||||
export const processEvent = async (
|
|
||||||
socket
|
|
||||||
) => {
|
|
||||||
// Only proceed if the socket is up, otherwise we throw the event into the void
|
|
||||||
if (!socket) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Only proceed if we're not already processing an event.
|
|
||||||
if (event_queue.length === 0 || event_processing) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set processing to true to block other events from being processed.
|
|
||||||
event_processing = true
|
|
||||||
|
|
||||||
// Apply the next event in the queue.
|
|
||||||
const event = event_queue.shift();
|
|
||||||
|
|
||||||
let eventSent = false
|
|
||||||
// Process events with handlers via REST and all others via websockets.
|
|
||||||
if (event.handler) {
|
|
||||||
eventSent = await applyRestEvent(event);
|
|
||||||
} else {
|
|
||||||
eventSent = await applyEvent(event, socket);
|
|
||||||
}
|
|
||||||
// If no event was sent, set processing to false.
|
|
||||||
if (!eventSent) {
|
|
||||||
event_processing = false;
|
|
||||||
// recursively call processEvent to drain the queue, since there is
|
|
||||||
// no state update to trigger the useEffect event loop.
|
|
||||||
await processEvent(socket)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Connect to a websocket and set the handlers.
|
|
||||||
* @param socket The socket object to connect.
|
|
||||||
* @param dispatch The function to queue state update
|
|
||||||
* @param transports The transports to use.
|
|
||||||
* @param setConnectError The function to update connection error value.
|
|
||||||
* @param client_storage The client storage object from context.js
|
|
||||||
*/
|
|
||||||
export const connect = async (
|
|
||||||
socket,
|
|
||||||
dispatch,
|
|
||||||
transports,
|
|
||||||
setConnectError,
|
|
||||||
client_storage = {},
|
|
||||||
) => {
|
|
||||||
// Get backend URL object from the endpoint.
|
|
||||||
const endpoint = getEventURL()
|
|
||||||
|
|
||||||
// Create the socket.
|
|
||||||
socket.current = io(endpoint.href, {
|
|
||||||
path: endpoint["pathname"],
|
|
||||||
transports: transports,
|
|
||||||
autoUnref: false,
|
|
||||||
});
|
|
||||||
|
|
||||||
// Once the socket is open, hydrate the page.
|
|
||||||
socket.current.on("connect", () => {
|
|
||||||
setConnectError(null)
|
|
||||||
});
|
|
||||||
|
|
||||||
socket.current.on('connect_error', (error) => {
|
|
||||||
setConnectError(error)
|
|
||||||
});
|
|
||||||
|
|
||||||
// On each received message, queue the updates and events.
|
|
||||||
socket.current.on("event", message => {
|
|
||||||
const update = JSON5.parse(message)
|
|
||||||
dispatch(update.delta)
|
|
||||||
applyClientStorageDelta(client_storage, update.delta)
|
|
||||||
event_processing = !update.final
|
|
||||||
if (update.events) {
|
|
||||||
queueEvents(update.events, socket)
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Upload files to the server.
|
|
||||||
*
|
|
||||||
* @param state The state to apply the delta to.
|
|
||||||
* @param handler The handler to use.
|
|
||||||
*
|
|
||||||
* @returns Whether the files were uploaded.
|
|
||||||
*/
|
|
||||||
export const uploadFiles = async (handler, files) => {
|
|
||||||
// return if there's no file to upload
|
|
||||||
if (files.length == 0) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const headers = {
|
|
||||||
"Content-Type": files[0].type,
|
|
||||||
};
|
|
||||||
const formdata = new FormData();
|
|
||||||
|
|
||||||
// Add the token and handler to the file name.
|
|
||||||
for (let i = 0; i < files.length; i++) {
|
|
||||||
formdata.append(
|
|
||||||
"files",
|
|
||||||
files[i],
|
|
||||||
getToken() + ":" + handler + ":" + files[i].name
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Send the file to the server.
|
|
||||||
await axios.post(UPLOADURL, formdata, headers)
|
|
||||||
.then(() => { return true; })
|
|
||||||
.catch(
|
|
||||||
error => {
|
|
||||||
if (error.response) {
|
|
||||||
// The request was made and the server responded with a status code
|
|
||||||
// that falls out of the range of 2xx
|
|
||||||
console.log(error.response.data);
|
|
||||||
} else if (error.request) {
|
|
||||||
// The request was made but no response was received
|
|
||||||
// `error.request` is an instance of XMLHttpRequest in the browser and an instance of
|
|
||||||
// http.ClientRequest in node.js
|
|
||||||
console.log(error.request);
|
|
||||||
} else {
|
|
||||||
// Something happened in setting up the request that triggered an Error
|
|
||||||
console.log(error.message);
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
)
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create an event object.
|
|
||||||
* @param name The name of the event.
|
|
||||||
* @param payload The payload of the event.
|
|
||||||
* @param handler The client handler to process event.
|
|
||||||
* @returns The event object.
|
|
||||||
*/
|
|
||||||
export const Event = (name, payload = {}, handler = null) => {
|
|
||||||
return { name, payload, handler };
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Package client-side storage values as payload to send to the
|
|
||||||
* backend with the hydrate event
|
|
||||||
* @param client_storage The client storage object from context.js
|
|
||||||
* @returns payload dict of client storage values
|
|
||||||
*/
|
|
||||||
export const hydrateClientStorage = (client_storage) => {
|
|
||||||
const client_storage_values = {
|
|
||||||
"cookies": {},
|
|
||||||
"local_storage": {}
|
|
||||||
}
|
|
||||||
if (client_storage.cookies) {
|
|
||||||
for (const state_key in client_storage.cookies) {
|
|
||||||
const cookie_options = client_storage.cookies[state_key]
|
|
||||||
const cookie_name = cookie_options.name || state_key
|
|
||||||
const cookie_value = cookies.get(cookie_name)
|
|
||||||
if (cookie_value !== undefined) {
|
|
||||||
client_storage_values.cookies[state_key] = cookies.get(cookie_name)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (client_storage.local_storage && (typeof window !== 'undefined')) {
|
|
||||||
for (const state_key in client_storage.local_storage) {
|
|
||||||
const options = client_storage.local_storage[state_key]
|
|
||||||
const local_storage_value = localStorage.getItem(options.name || state_key)
|
|
||||||
if (local_storage_value !== null) {
|
|
||||||
client_storage_values.local_storage[state_key] = local_storage_value
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (client_storage.cookies || client_storage.local_storage) {
|
|
||||||
return client_storage_values
|
|
||||||
}
|
|
||||||
return {}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Update client storage values based on backend state delta.
|
|
||||||
* @param client_storage The client storage object from context.js
|
|
||||||
* @param delta The state update from the backend
|
|
||||||
*/
|
|
||||||
const applyClientStorageDelta = (client_storage, delta) => {
|
|
||||||
// find the main state and check for is_hydrated
|
|
||||||
const unqualified_states = Object.keys(delta).filter((key) => key.split(".").length === 1);
|
|
||||||
if (unqualified_states.length === 1) {
|
|
||||||
const main_state = delta[unqualified_states[0]]
|
|
||||||
if (main_state.is_hydrated !== undefined && !main_state.is_hydrated) {
|
|
||||||
// skip if the state is not hydrated yet, since all client storage
|
|
||||||
// values are sent in the hydrate event
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Save known client storage values to cookies and localStorage.
|
|
||||||
for (const substate in delta) {
|
|
||||||
for (const key in delta[substate]) {
|
|
||||||
const state_key = `${substate}.${key}`
|
|
||||||
if (client_storage.cookies && state_key in client_storage.cookies) {
|
|
||||||
const cookie_options = { ...client_storage.cookies[state_key] }
|
|
||||||
const cookie_name = cookie_options.name || state_key
|
|
||||||
delete cookie_options.name // name is not a valid cookie option
|
|
||||||
cookies.set(cookie_name, delta[substate][key], cookie_options);
|
|
||||||
} else if (client_storage.local_storage && state_key in client_storage.local_storage && (typeof window !== 'undefined')) {
|
|
||||||
const options = client_storage.local_storage[state_key]
|
|
||||||
localStorage.setItem(options.name || state_key, delta[substate][key]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Establish websocket event loop for a NextJS page.
|
|
||||||
* @param initial_state The initial app state.
|
|
||||||
* @param initial_events Function that returns the initial app events.
|
|
||||||
* @param client_storage The client storage object from context.js
|
|
||||||
*
|
|
||||||
* @returns [state, addEvents, connectError] -
|
|
||||||
* state is a reactive dict,
|
|
||||||
* addEvents is used to queue an event, and
|
|
||||||
* connectError is a reactive js error from the websocket connection (or null if connected).
|
|
||||||
*/
|
|
||||||
export const useEventLoop = (
|
|
||||||
initial_state = {},
|
|
||||||
initial_events = () => [],
|
|
||||||
client_storage = {},
|
|
||||||
) => {
|
|
||||||
const socket = useRef(null)
|
|
||||||
const router = useRouter()
|
|
||||||
const [state, dispatch] = useReducer(applyDelta, initial_state)
|
|
||||||
const [connectError, setConnectError] = useState(null)
|
|
||||||
|
|
||||||
// Function to add new events to the event queue.
|
|
||||||
const addEvents = (events, _e, event_actions) => {
|
|
||||||
if (event_actions?.preventDefault && _e?.preventDefault) {
|
|
||||||
_e.preventDefault();
|
|
||||||
}
|
|
||||||
if (event_actions?.stopPropagation && _e?.stopPropagation) {
|
|
||||||
_e.stopPropagation();
|
|
||||||
}
|
|
||||||
queueEvents(events, socket)
|
|
||||||
}
|
|
||||||
|
|
||||||
const sentHydrate = useRef(false); // Avoid double-hydrate due to React strict-mode
|
|
||||||
// initial state hydrate
|
|
||||||
useEffect(() => {
|
|
||||||
if (router.isReady && !sentHydrate.current) {
|
|
||||||
addEvents(initial_events())
|
|
||||||
sentHydrate.current = true
|
|
||||||
}
|
|
||||||
}, [router.isReady])
|
|
||||||
|
|
||||||
// Main event loop.
|
|
||||||
useEffect(() => {
|
|
||||||
// Skip if the router is not ready.
|
|
||||||
if (!router.isReady) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// only use websockets if state is present
|
|
||||||
if (Object.keys(state).length > 0) {
|
|
||||||
// Initialize the websocket connection.
|
|
||||||
if (!socket.current) {
|
|
||||||
connect(socket, dispatch, ['websocket', 'polling'], setConnectError, client_storage)
|
|
||||||
}
|
|
||||||
(async () => {
|
|
||||||
// Process all outstanding events.
|
|
||||||
while (event_queue.length > 0 && !event_processing) {
|
|
||||||
await processEvent(socket.current)
|
|
||||||
}
|
|
||||||
})()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
return [state, addEvents, connectError]
|
|
||||||
}
|
|
||||||
|
|
||||||
/***
|
|
||||||
* Check if a value is truthy in python.
|
|
||||||
* @param val The value to check.
|
|
||||||
* @returns True if the value is truthy, false otherwise.
|
|
||||||
*/
|
|
||||||
export const isTrue = (val) => {
|
|
||||||
return Array.isArray(val) ? val.length > 0 : !!val;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the value from a ref.
|
|
||||||
* @param ref The ref to get the value from.
|
|
||||||
* @returns The value.
|
|
||||||
*/
|
|
||||||
export const getRefValue = (ref) => {
|
|
||||||
if (!ref || !ref.current) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (ref.current.type == "checkbox") {
|
|
||||||
return ref.current.checked;
|
|
||||||
} else {
|
|
||||||
//querySelector(":checked") is needed to get value from radio_group
|
|
||||||
return ref.current.value || (ref.current.querySelector(':checked') && ref.current.querySelector(':checked').value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the values from a ref array.
|
|
||||||
* @param refs The refs to get the values from.
|
|
||||||
* @returns The values array.
|
|
||||||
*/
|
|
||||||
export const getRefValues = (refs) => {
|
|
||||||
if (!refs) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// getAttribute is used by RangeSlider because it doesn't assign value
|
|
||||||
return refs.map((ref) => ref.current.value || ref.current.getAttribute("aria-valuenow"));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Spread two arrays or two objects.
|
|
||||||
* @param first The first array or object.
|
|
||||||
* @param second The second array or object.
|
|
||||||
* @returns The final merged array or object.
|
|
||||||
*/
|
|
||||||
export const spreadArraysOrObjects = (first, second) => {
|
|
||||||
if (Array.isArray(first) && Array.isArray(second)) {
|
|
||||||
return [...first, ...second];
|
|
||||||
} else if (typeof first === 'object' && typeof second === 'object') {
|
|
||||||
return { ...first, ...second };
|
|
||||||
} else {
|
|
||||||
throw new Error('Both parameters must be either arrays or objects.');
|
|
||||||
}
|
|
||||||
}
|
|
@ -1 +0,0 @@
|
|||||||
export default {"styles": {"global": {"body": {}}}}
|
|
Loading…
Reference in New Issue
Block a user