BLUR-25 Wrzucenie bazowej wersji aplikacji na uniwerysteckie repo

This commit is contained in:
markrz12 2023-11-17 14:22:28 +01:00
parent 4601be5274
commit 5307113dc3
52 changed files with 6769 additions and 1 deletions

4
.gitignore vendored Normal file
View File

@ -0,0 +1,4 @@
*.db
*.py[cod]
.web
__pycache__/

39
.web/.gitignore vendored Normal file
View File

@ -0,0 +1,39 @@
# 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

View File

@ -0,0 +1,21 @@
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>
)
}

View File

@ -0,0 +1,20 @@
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
.web/env.json Normal file
View File

@ -0,0 +1 @@
{"PING": "http://localhost:8000/ping", "EVENT": "ws://localhost:8000/_event", "UPLOAD": "http://localhost:8000/_upload"}

8
.web/jsconfig.json Normal file
View File

@ -0,0 +1,8 @@
{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@/*": ["public/*"]
}
}
}

6
.web/next.config.js Normal file
View File

@ -0,0 +1,6 @@
module.exports = {
basePath: "",
compress: true,
reactStrictMode: true,
trailingSlash: true,
};

3650
.web/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

32
.web/package.json Normal file
View File

@ -0,0 +1,32 @@
{
"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",
"socket.io-client": "4.6.1",
"tailwindcss": "^3.3.2",
"universal-cookie": "4.0.4"
},
"devDependencies": {
"autoprefixer": "10.4.14",
"postcss": "8.4.24"
}
}

88
.web/pages/404.js Normal file
View File

@ -0,0 +1,88 @@
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>
)
}

46
.web/pages/_app.js Normal file
View File

@ -0,0 +1,46 @@
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>
);
}

15
.web/pages/_document.js Normal file
View File

@ -0,0 +1,15 @@
import { Head, Html, Main, NextScript } from "next/document"
export default function Document() {
return (
<Html>
<Head/>
<body>
<Main/>
<NextScript/>
</body>
</Html>
)
}

164
.web/pages/dashboard.js Normal file
View File

@ -0,0 +1,164 @@
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")) ? `#fcd2e7` : `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")) ? `#fcd2e7` : `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")) ? `#fcd2e7` : `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": "#fcd2e7"}}}>
<Link as={NextLink} href={`/`} sx={{"width": "100%"}}>
{`Strona główna`}
</Link>
</MenuItem>
<MenuItem sx={{"_hover": {"bg": "#fcd2e7"}}}>
<Link as={NextLink} href={`/dashboard`} sx={{"width": "100%"}}>
{`Dashboard`}
</Link>
</MenuItem>
<MenuItem sx={{"_hover": {"bg": "#fcd2e7"}}}>
<Link as={NextLink} href={`/settings`} sx={{"width": "100%"}}>
{`Settings`}
</Link>
</MenuItem>
<MenuDivider/>
<MenuItem sx={{"_hover": {"bg": "#fcd2e7"}}}>
<Link as={NextLink} href={`https://github.com/reflex-dev`} sx={{"width": "100%"}}>
{`About`}
</Link>
</MenuItem>
<MenuItem sx={{"_hover": {"bg": "#fcd2e7"}}}>
<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>
)
}

233
.web/pages/index.js Normal file
View File

@ -0,0 +1,233 @@
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, Heading, HStack, Image, Link, List, ListItem, Menu, MenuButton, MenuDivider, MenuItem, MenuList, Modal, ModalBody, ModalContent, ModalHeader, ModalOverlay, OrderedList, 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")) ? `#fcd2e7` : `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")) ? `#fcd2e7` : `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")) ? `#fcd2e7` : `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>
<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`}>
{`Prost 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ć nieruszoną, a my zadbasz 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 Może Ci Się Przydać?`}
</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": "center"}}>
{`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": "#fcd2e7"}}}>
<Link as={NextLink} href={`/`} sx={{"width": "100%"}}>
{`Strona główna`}
</Link>
</MenuItem>
<MenuItem sx={{"_hover": {"bg": "#fcd2e7"}}}>
<Link as={NextLink} href={`/dashboard`} sx={{"width": "100%"}}>
{`Dashboard`}
</Link>
</MenuItem>
<MenuItem sx={{"_hover": {"bg": "#fcd2e7"}}}>
<Link as={NextLink} href={`/settings`} sx={{"width": "100%"}}>
{`Settings`}
</Link>
</MenuItem>
<MenuDivider/>
<MenuItem sx={{"_hover": {"bg": "#fcd2e7"}}}>
<Link as={NextLink} href={`https://github.com/reflex-dev`} sx={{"width": "100%"}}>
{`About`}
</Link>
</MenuItem>
<MenuItem sx={{"_hover": {"bg": "#fcd2e7"}}}>
<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>
)
}

164
.web/pages/settings.js Normal file
View File

@ -0,0 +1,164 @@
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")) ? `#fcd2e7` : `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")) ? `#fcd2e7` : `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")) ? `#fcd2e7` : `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"}}>
{`Settings`}
</Heading>
<Text>
{`Welcome to Reflex!`}
</Text>
<Text>
{`You can edit this page in `}
<Code>
{`{your_app}/pages/settings.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": "#fcd2e7"}}}>
<Link as={NextLink} href={`/`} sx={{"width": "100%"}}>
{`Strona główna`}
</Link>
</MenuItem>
<MenuItem sx={{"_hover": {"bg": "#fcd2e7"}}}>
<Link as={NextLink} href={`/dashboard`} sx={{"width": "100%"}}>
{`Dashboard`}
</Link>
</MenuItem>
<MenuItem sx={{"_hover": {"bg": "#fcd2e7"}}}>
<Link as={NextLink} href={`/settings`} sx={{"width": "100%"}}>
{`Settings`}
</Link>
</MenuItem>
<MenuDivider/>
<MenuItem sx={{"_hover": {"bg": "#fcd2e7"}}}>
<Link as={NextLink} href={`https://github.com/reflex-dev`} sx={{"width": "100%"}}>
{`About`}
</Link>
</MenuItem>
<MenuItem sx={{"_hover": {"bg": "#fcd2e7"}}}>
<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>
)
}

6
.web/postcss.config.js Normal file
View File

@ -0,0 +1,6 @@
module.exports = {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
}

BIN
.web/public/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

10
.web/public/github.svg Normal file
View File

@ -0,0 +1,10 @@
<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>

After

Width:  |  Height:  |  Size: 1.4 KiB

BIN
.web/public/icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 95 KiB

BIN
.web/public/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 93 KiB

13
.web/public/paneleft.svg Normal file
View File

@ -0,0 +1,13 @@
<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>

After

Width:  |  Height:  |  Size: 807 B

1
.web/reflex.json Normal file
View File

@ -0,0 +1 @@
{"version": "0.3.2", "project_hash": 157481488270591489013065351347599429566}

1015
.web/styles/code/prism.js Normal file

File diff suppressed because it is too large Load Diff

1
.web/styles/styles.css Normal file
View File

@ -0,0 +1 @@
@import url('./tailwind.css');

3
.web/styles/tailwind.css Normal file
View File

@ -0,0 +1,3 @@
@tailwind base;
@tailwind components;
@tailwind utilities;

7
.web/tailwind.config.js Normal file
View File

@ -0,0 +1,7 @@
/** @type {import('tailwindcss').Config} */
module.exports = {
content: ["./pages/**/*.{js,ts,jsx,tsx}"],
theme: null,
plugins: [
],
};

View File

@ -0,0 +1,36 @@
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
}

5
.web/utils/components.js Normal file
View File

@ -0,0 +1,5 @@
import { memo } from "react"
import { E, isTrue } from "/utils/state"

30
.web/utils/context.js Normal file
View File

@ -0,0 +1,30 @@
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>
)
}

View File

@ -0,0 +1,67 @@
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 };
}

589
.web/utils/state.js Normal file
View File

@ -0,0 +1,589 @@
// 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
.web/utils/theme.js Normal file
View File

@ -0,0 +1 @@
export default {"styles": {"global": {"body": {}}}}

View File

@ -1,2 +1,27 @@
# BlurMe
# Witaj w BlurMe!
Dlaczego Blurme?
Prywatność: Nasza aplikacja zapewnia pełną ochronę prywatności, eliminując ryzyko identyfikacji osób niezwiązanych z anonimizacją.
Prost obsługa: Intuicyjny interfejs użytkownika sprawia, że korzystanie z Blurme jest łatwe dla każdego.
Elastyczność: Wybierz, która twarz na zdjęciu ma pozostać nieruszoną, a my zadbasz o resztę. Indywidualizuj swoje zdjęcia zgodnie z własnymi potrzebami.
Jak to działa?
Wgraj Zdjęcie: Prosty proces rozpoczyna się od dodania zdjęcia, które chcesz anonimizować.
Zaznacz Twarz do Ochrony: Oznacz twarz, którą chcesz zachować.
Anonimizuj Resztę: Naciśnij przycisk, a Blurme automatycznie zastosuje efekt rozmycia do wszystkich obszarów poza zaznaczoną twarzą.
Pobierz i Podziel Się: Gotowe! Pobierz anonimizowane zdjęcie i dziel się nim bez obaw o prywatność.
Do Czego Może Ci Się Przydać?
Ochrona Tożsamości: Idealne do usuwania twarzy nieznajomych lub przypadkowych przechodniów z tła.
Zachowanie Prywatności: Przydatne w sytuacjach, gdzie chcesz udostępnić zdjęcia publicznie, ale z zachowaniem prywatności pewnych osób.
Kreatywność: Wyraź swoją kreatywność, zachowując jednocześnie istotne elementy na zdjęciach.
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!
In this template, the base state handles the toggle for the sidebar.
As your app grows, we recommend using [substates](https://reflex.dev/docs/state/substates/)
to organize your state. You can either define substates in their own files, or if the state is
specific to a page, you can define it in the page file itself.

BIN
assets/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

10
assets/github.svg Normal file
View File

@ -0,0 +1,10 @@
<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>

After

Width:  |  Height:  |  Size: 1.4 KiB

BIN
assets/icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 95 KiB

BIN
assets/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 93 KiB

13
assets/paneleft.svg Normal file
View File

@ -0,0 +1,13 @@
<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>

After

Width:  |  Height:  |  Size: 807 B

1
blurme/__init__.py Normal file
View File

@ -0,0 +1 @@
"""Base template for Reflex."""

12
blurme/blurme.py Normal file
View File

@ -0,0 +1,12 @@
"""Welcome to Reflex!."""
from blurme import styles
# Import all the pages.
from blurme.pages import *
import reflex as rx
# Create the app and compile it.
app = rx.App(style=styles.base_style)
app.compile()

View File

View File

@ -0,0 +1,134 @@
"""Sidebar component for the app."""
from blurme import styles
from blurme.state import State
import reflex as rx
def sidebar_header() -> rx.Component:
"""Sidebar header.
Returns:
The sidebar header component.
"""
return rx.hstack(
# The logo.
rx.image(
src="/icon.png",
height="4.5em",
),
rx.spacer(),
# Link to Reflex GitHub repo.
width="100%",
border_bottom=styles.border,
padding="1em",
)
#def sidebar_footer() -> rx.Component:
"""Sidebar footer.
Returns:
The sidebar footer component.
"""
# return rx.hstack(
# rx.spacer(),
# rx.link(
# rx.text("Docs"),
# href="https://reflex.dev/docs/getting-started/introduction/",
# ),
# rx.link(
# rx.text("Blog"),
# href="https://reflex.dev/blog/",
# ),
# width="100%",
# border_top=styles.border,
# padding="1em",
# )
def sidebar_item(text: str, url: str) -> rx.Component:
#icon: str,
"""Sidebar item.
Args:
text: The text of the item.
icon: The icon of the item.
url: The URL of the item.
Returns:
rx.Component: The sidebar item component.
"""
# Whether the item is active.
active = (State.router.page.path == f"/{text.lower()}") | (
(State.router.page.path == "/") & text == "Home"
)
return rx.link(
rx.hstack(
#rx.image(
# src=icon,
# height="2.5em",
# padding="0.5em",
#),
rx.text(
text,
),
bg=rx.cond(
active,
styles.accent_color,
"transparent",
),
color=rx.cond(
active,
styles.accent_text_color,
styles.text_color,
),
border_radius=styles.border_radius,
box_shadow=styles.box_shadow,
width="100%",
padding_x="1em",
height = "2em"
),
href=url,
width="100%",
)
def sidebar() -> rx.Component:
"""The sidebar.
Returns:
The sidebar component.
"""
# Get all the decorated pages and add them to the sidebar.
from reflex.page import get_decorated_pages
return rx.box(
rx.vstack(
sidebar_header(),
rx.vstack(
*[
sidebar_item(
text=page.get("title", page["route"].strip("/").capitalize()),
#icon=page.get("image", "/github.svg"),
url=page["route"],
)
for page in get_decorated_pages()
],
width="100%",
overflow_y="auto",
align_items="flex-start",
padding="1em",
),
rx.spacer(),
#sidebar_footer(),
height="100dvh",
),
display=["none", "none", "block"],
min_width=styles.sidebar_width,
height="100%",
position="sticky",
top="0px",
border_right=styles.border,
)

3
blurme/pages/__init__.py Normal file
View File

@ -0,0 +1,3 @@
from .dashboard import dashboard
from .index import index
from .settings import settings

21
blurme/pages/dashboard.py Normal file
View File

@ -0,0 +1,21 @@
"""The dashboard page."""
from blurme.templates import template
import reflex as rx
@template(route="/dashboard", title="Dashboard")
def dashboard() -> rx.Component:
"""The dashboard page.
Returns:
The UI for the dashboard page.
"""
return rx.vstack(
rx.heading("Dashboard", font_size="3em"),
rx.text("Welcome to Reflex!"),
rx.text(
"You can edit this page in ",
rx.code("{your_app}/pages/dashboard.py"),
),
)

38
blurme/pages/index.py Normal file
View File

@ -0,0 +1,38 @@
"""The home page of the app."""
from blurme import styles
from blurme.templates import template
import reflex as rx
@template(route="/", title="Strona główna", image="/github.svg")
def index() -> rx.Component:
"""The home page.
Returns:
The UI for the home page.
"""
return rx.vstack(
rx.heading("Witaj w BlurMe!", font_size="3em", margin_bottom="25px", background_image="linear-gradient(271.68deg, #7566fe 0.75%, #f96caf 88.52%)", background_clip="text", padding = "10px"),
rx.text("Dlaczego Blurme?", font_size="1.5em", font_weight="bold", margin_top="10px", margin_bottom="10px"),
rx.list(
rx.list_item(rx.text("Prywatność: ",as_="b"), "Nasza aplikacja zapewnia pełną ochronę prywatności, eliminując ryzyko identyfikacji osób niezwiązanych z anonimizacją."),
rx.list_item(rx.text("Prost obsługa: ",as_="b"),"Intuicyjny interfejs użytkownika sprawia, że korzystanie z Blurme jest łatwe dla każdego."),
rx.list_item(rx.text("Elastyczność: ",as_="b"),"Wybierz, która twarz na zdjęciu ma pozostać nieruszoną, a my zadbasz o resztę. Indywidualizuj swoje zdjęcia zgodnie z własnymi potrzebami."),
text_align="left"),
rx.text("Jak to działa?", font_size="1.5em", font_weight="bold", margin_top="20px", margin_bottom="10px"),
rx.ordered_list(
rx.list_item(rx.text("Wgraj Zdjęcie: ",as_="b"), "Prosty proces rozpoczyna się od dodania zdjęcia, które chcesz anonimizować."),
rx.list_item(rx.text("Zaznacz Twarz do Ochrony: ",as_="b"), "Oznacz twarz, którą chcesz zachować."),
rx.list_item(rx.text("Anonimizuj Resztę: ",as_="b"), "Naciśnij przycisk, a Blurme automatycznie zastosuje efekt rozmycia do wszystkich obszarów poza zaznaczoną twarzą."),
rx.list_item(rx.text("Pobierz i Podziel Się: ",as_="b"), "Gotowe! Pobierz anonimizowane zdjęcie i dziel się nim bez obaw o prywatność.")
),
rx.text("Do Czego Może Ci Się Przydać?", font_size="1.5em", font_weight="bold", margin_top="20px", margin_bottom="10px"),
rx.list(
rx.list_item(rx.text("Ochrona Tożsamości: ",as_="b"),"Idealne do usuwania twarzy nieznajomych lub przypadkowych przechodniów z tła.", text_align="left"),
rx.list_item(rx.text("Zachowanie Prywatności: ",as_="b"),"Przydatne w sytuacjach, gdzie chcesz udostępnić zdjęcia publicznie, ale z zachowaniem prywatności pewnych osób.", text_align="left"),
rx.list_item(rx.text("Kreatywność: ",as_="b"),"Wyraź swoją kreatywność, zachowując jednocześnie istotne elementy na zdjęciach.", text_align="left"),
),
rx.text("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_align = "center"),
)

22
blurme/pages/settings.py Normal file
View File

@ -0,0 +1,22 @@
"""The settings page."""
from blurme.templates import template
import reflex as rx
@template(route="/settings", title="Settings")
def settings() -> rx.Component:
"""The settings page.
Returns:
The UI for the settings page.
"""
return rx.vstack(
rx.heading("Settings", font_size="3em"),
rx.text("Welcome to Reflex!"),
rx.text(
"You can edit this page in ",
rx.code("{your_app}/pages/settings.py"),
),
)

12
blurme/state.py Normal file
View File

@ -0,0 +1,12 @@
"""Base state for the app."""
import reflex as rx
class State(rx.State):
"""Base state for the app.
The base state is used to store general vars used throughout the app.
"""
pass

67
blurme/styles.py Normal file
View File

@ -0,0 +1,67 @@
"""Styles for the app."""
import reflex as rx
border_radius = "0.375rem"
box_shadow = "0px 0px 0px 1px rgba(84, 82, 95, 0.14)"
border = "1px solid #F4F3F6"
text_color = "black"
accent_text_color = "#1A1060"
accent_color = "#fcd2e7"
hover_accent_color = {"_hover": {"color": accent_color}}
hover_accent_bg = {"_hover": {"bg": accent_color}}
content_width_vw = "90vw"
sidebar_width = "20em"
template_page_style = {"padding_top": "5em", "padding_x": ["auto", "2em"], "flex": "1"}
template_content_style = {
"align_items": "flex-start",
"box_shadow": box_shadow,
"border_radius": border_radius,
"padding": "1em",
"margin_bottom": "2em",
}
link_style = {
"color": text_color,
"text_decoration": "none",
**hover_accent_color,
}
overlapping_button_style = {
"background_color": "white",
"border": border,
"border_radius": border_radius,
}
base_style = {
rx.MenuButton: {
"width": "3em",
"height": "3em",
**overlapping_button_style,
},
rx.MenuItem: hover_accent_bg,
}
markdown_style = {
"code": lambda text: rx.code(text, color="#1F1944", bg="#EAE4FD", margin="10px 0"),
"a": lambda text, **props: rx.link(
text,
**props,
font_weight="bold",
color="#03030B",
text_decoration="underline",
text_decoration_color="#AD9BF8",
_hover={
"color": "#AD9BF8",
"text_decoration": "underline",
"text_decoration_color": "#03030B",
},
margin="10px 0",
),
"ul": lambda items: rx.unordered_list(items, margin="10px 0", padding_left="20px"),
"ol": lambda items: rx.ordered_list(items, margin="10px 0", padding_left="20px"),
"p": lambda text: rx.text(text, margin="10px 0"),
}

View File

@ -0,0 +1 @@
from .template import template

View File

@ -0,0 +1,127 @@
"""Common templates used between pages in the app."""
from __future__ import annotations
from blurme import styles
from blurme.components.sidebar import sidebar
from typing import Callable
import reflex as rx
# Meta tags for the app.
default_meta = [
{
"name": "viewport",
"content": "width=device-width, shrink-to-fit=no, initial-scale=1",
},
]
def menu_button() -> rx.Component:
"""The menu button on the top right of the page.
Returns:
The menu button component.
"""
from reflex.page import get_decorated_pages
return rx.box(
rx.menu(
rx.menu_button(
rx.icon(
tag="hamburger",
size="4em",
color=styles.text_color,
),
),
rx.menu_list(
*[
rx.menu_item(
rx.link(
page["title"],
href=page["route"],
width="100%",
)
)
for page in get_decorated_pages()
],
rx.menu_divider(),
rx.menu_item(
rx.link("About", href="https://github.com/reflex-dev", width="100%")
),
rx.menu_item(
rx.link("Contact", href="mailto:founders@=reflex.dev", width="100%")
),
),
),
position="fixed",
right="1.5em",
top="1.5em",
z_index="500",
)
def template(
route: str | None = None,
title: str | None = None,
image: str | None = None,
description: str | None = None,
meta: str | None = None,
script_tags: list[rx.Component] | None = None,
on_load: rx.event.EventHandler | list[rx.event.EventHandler] | None = None,
) -> Callable[[Callable[[], rx.Component]], rx.Component]:
"""The template for each page of the app.
Args:
route: The route to reach the page.
title: The title of the page.
image: The favicon of the page.
description: The description of the page.
meta: Additionnal meta to add to the page.
on_load: The event handler(s) called when the page load.
script_tags: Scripts to attach to the page.
Returns:
The template with the page content.
"""
def decorator(page_content: Callable[[], rx.Component]) -> rx.Component:
"""The template for each page of the app.
Args:
page_content: The content of the page.
Returns:
The template with the page content.
"""
# Get the meta tags for the page.
all_meta = [*default_meta, *(meta or [])]
@rx.page(
route=route,
title=title,
image=image,
description=description,
meta=all_meta,
script_tags=script_tags,
on_load=on_load,
)
def templated_page():
return rx.hstack(
sidebar(),
rx.box(
rx.box(
page_content(),
**styles.template_content_style,
),
**styles.template_page_style,
),
menu_button(),
align_items="flex-start",
transition="left 0.5s, width 0.5s",
position="relative",
)
return templated_page
return decorator

2
requirements.txt Normal file
View File

@ -0,0 +1,2 @@
reflex==0.3.2

5
rxconfig.py Normal file
View File

@ -0,0 +1,5 @@
import reflex as rx
config = rx.Config(
app_name="blurme",
)