Merge remote-tracking branch 'origin/master'
3
.idea/.gitignore
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
# Default ignored files
|
||||
/shelf/
|
||||
/workspace.xml
|
10
.idea/BlurMe.iml
Normal file
@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module type="PYTHON_MODULE" version="4">
|
||||
<component name="NewModuleRootManager">
|
||||
<content url="file://$MODULE_DIR$">
|
||||
<excludeFolder url="file://$MODULE_DIR$/venv" />
|
||||
</content>
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
</component>
|
||||
</module>
|
6
.idea/inspectionProfiles/profiles_settings.xml
Normal file
@ -0,0 +1,6 @@
|
||||
<component name="InspectionProjectProfileManager">
|
||||
<settings>
|
||||
<option name="USE_PROJECT_PROFILE" value="false" />
|
||||
<version value="1.0" />
|
||||
</settings>
|
||||
</component>
|
7
.idea/misc.xml
Normal file
@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.11 (BlurMe)" project-jdk-type="Python SDK" />
|
||||
<component name="PythonCompatibilityInspectionAdvertiser">
|
||||
<option name="version" value="3" />
|
||||
</component>
|
||||
</project>
|
8
.idea/modules.xml
Normal file
@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectModuleManager">
|
||||
<modules>
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/BlurMe.iml" filepath="$PROJECT_DIR$/.idea/BlurMe.iml" />
|
||||
</modules>
|
||||
</component>
|
||||
</project>
|
6
.idea/vcs.xml
Normal file
@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
||||
</component>
|
||||
</project>
|
39
.web/.gitignore
vendored
Normal 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
|
21
.web/components/reflex/chakra_color_mode_provider.js
Normal 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>
|
||||
)
|
||||
}
|
20
.web/components/reflex/radix_themes_color_mode_provider.js
Normal 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
@ -0,0 +1 @@
|
||||
{"PING": "http://localhost:8000/ping", "EVENT": "ws://localhost:8000/_event", "UPLOAD": "http://localhost:8000/_upload"}
|
8
.web/jsconfig.json
Normal file
@ -0,0 +1,8 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"@/*": ["public/*"]
|
||||
}
|
||||
}
|
||||
}
|
6
.web/next.config.js
Normal file
@ -0,0 +1,6 @@
|
||||
module.exports = {
|
||||
basePath: "",
|
||||
compress: true,
|
||||
reactStrictMode: true,
|
||||
trailingSlash: true,
|
||||
};
|
5557
.web/package-lock.json
generated
Normal file
39
.web/package.json
Normal file
@ -0,0 +1,39 @@
|
||||
{
|
||||
"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",
|
||||
"react-markdown": "^8.0.7",
|
||||
"react-syntax-highlighter": "^15.5.0",
|
||||
"rehype-katex": "^6.0.3",
|
||||
"rehype-raw": "^6.1.1",
|
||||
"remark-gfm": "^3.0.1",
|
||||
"remark-math": "^5.1.1",
|
||||
"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
@ -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 Error from "next/error"
|
||||
import { useClientSideRouting } from "/utils/client_side_routing"
|
||||
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
@ -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
@ -0,0 +1,15 @@
|
||||
import { Head, Html, Main, NextScript } from "next/document"
|
||||
|
||||
|
||||
|
||||
export default function Document() {
|
||||
return (
|
||||
<Html>
|
||||
<Head/>
|
||||
<body>
|
||||
<Main/>
|
||||
<NextScript/>
|
||||
</body>
|
||||
</Html>
|
||||
)
|
||||
}
|
182
.web/pages/dashboard.js
Normal file
@ -0,0 +1,182 @@
|
||||
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, 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": "100em"}}>
|
||||
<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": "1.3em", "height": "2.5em"}}>
|
||||
<Image src={`/home-icon.png`} sx={{"height": "2.5em", "padding": "0.5em"}}/>
|
||||
<Text>
|
||||
{`Strona główna`}
|
||||
</Text>
|
||||
</HStack>
|
||||
</Link>
|
||||
<Link as={NextLink} href={`/dashboard`} sx={{"width": "100%"}}>
|
||||
<HStack sx={{"bg": isTrue((state.router.page.path === "/kontakt") || (((state.router.page.path === "/") && "Kontakt") === "Home")) ? `#fff0f7` : `transparent`, "color": isTrue((state.router.page.path === "/kontakt") || (((state.router.page.path === "/") && "Kontakt") === "Home")) ? `#1A1060` : `black`, "borderRadius": "0.375rem", "boxShadow": "0px 0px 0px 1px rgba(84, 82, 95, 0.14)", "width": "100%", "paddingX": "1.3em", "height": "2.5em"}}>
|
||||
<Image src={`/contact-icon.png`} sx={{"height": "2.5em", "padding": "0.5em"}}/>
|
||||
<Text>
|
||||
{`Kontakt`}
|
||||
</Text>
|
||||
</HStack>
|
||||
</Link>
|
||||
<Link as={NextLink} href={`/settings`} sx={{"width": "100%"}}>
|
||||
<HStack sx={{"bg": isTrue((state.router.page.path === "/zdj\\u0119cie") || (((state.router.page.path === "/") && "Zdj\\u0119cie") === "Home")) ? `#fff0f7` : `transparent`, "color": isTrue((state.router.page.path === "/zdj\\u0119cie") || (((state.router.page.path === "/") && "Zdj\\u0119cie") === "Home")) ? `#1A1060` : `black`, "borderRadius": "0.375rem", "boxShadow": "0px 0px 0px 1px rgba(84, 82, 95, 0.14)", "width": "100%", "paddingX": "1.3em", "height": "2.5em"}}>
|
||||
<Image src={`/image-icon.png`} sx={{"height": "2.5em", "padding": "0.5em"}}/>
|
||||
<Text>
|
||||
{`Zdjęcie`}
|
||||
</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"}}>
|
||||
<Fragment>
|
||||
<Heading sx={{"fontSize": "3em"}}>
|
||||
{`Kontakt`}
|
||||
</Heading>
|
||||
<Text>
|
||||
{`Witaj na stronie Kontaktowej w aplikacji BlurMe!`}
|
||||
</Text>
|
||||
<Text>
|
||||
{`Jesteśmy dostępni dla Ciebie na różnych platformach. Skontaktuj się z nami, gdy tylko masz pytania, sugestie lub po prostu chcesz porozmawiać o naszej aplikacji.`}
|
||||
</Text>
|
||||
<Heading sx={{"level": 3}}>
|
||||
{`E-mail:`}
|
||||
</Heading>
|
||||
<Text>
|
||||
{`Zapraszamy do napisania do nas na adres: contact@blurme.com`}
|
||||
</Text>
|
||||
<Heading sx={{"level": 3}}>
|
||||
{`Telefon:`}
|
||||
</Heading>
|
||||
<Text>
|
||||
{`Możesz także zadzwonić pod numer: +48 123 456 789`}
|
||||
</Text>
|
||||
<Heading sx={{"level": 3}}>
|
||||
{`Media Społecznościowe:`}
|
||||
</Heading>
|
||||
<Text>
|
||||
{`Odwiedź nas na Facebooku, Twitterze i Instagramie @BlurMeApp`}
|
||||
</Text>
|
||||
</Fragment>
|
||||
</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%"}}>
|
||||
{`Kontakt`}
|
||||
</Link>
|
||||
</MenuItem>
|
||||
<MenuItem sx={{"_hover": {"bg": "#fff0f7"}}}>
|
||||
<Link as={NextLink} href={`/settings`} sx={{"width": "100%"}}>
|
||||
{`Zdjęcie`}
|
||||
</Link>
|
||||
</MenuItem>
|
||||
<MenuDivider/>
|
||||
<MenuItem sx={{"_hover": {"bg": "#fff0f7"}}}>
|
||||
<Link as={NextLink} href={`/about`} 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>
|
||||
{`Kontakt`}
|
||||
</title>
|
||||
<meta content={`A Reflex app.`} name={`description`}/>
|
||||
<meta content={`/contact-icon.png`} property={`og:image`}/>
|
||||
<meta content={`width=device-width, shrink-to-fit=no, initial-scale=1`} name={`viewport`}/>
|
||||
</NextHead>
|
||||
</Fragment>
|
||||
)
|
||||
}
|
279
.web/pages/index.js
Normal file
@ -0,0 +1,279 @@
|
||||
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, Flex, Heading, HStack, Image, Link, List, ListItem, Menu, MenuButton, MenuDivider, MenuItem, MenuList, Modal, ModalBody, ModalContent, ModalHeader, ModalOverlay, 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": "100em"}}>
|
||||
<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": "1.3em", "height": "2.5em"}}>
|
||||
<Image src={`/home-icon.png`} sx={{"height": "2.5em", "padding": "0.5em"}}/>
|
||||
<Text>
|
||||
{`Strona główna`}
|
||||
</Text>
|
||||
</HStack>
|
||||
</Link>
|
||||
<Link as={NextLink} href={`/dashboard`} sx={{"width": "100%"}}>
|
||||
<HStack sx={{"bg": isTrue((state.router.page.path === "/kontakt") || (((state.router.page.path === "/") && "Kontakt") === "Home")) ? `#fff0f7` : `transparent`, "color": isTrue((state.router.page.path === "/kontakt") || (((state.router.page.path === "/") && "Kontakt") === "Home")) ? `#1A1060` : `black`, "borderRadius": "0.375rem", "boxShadow": "0px 0px 0px 1px rgba(84, 82, 95, 0.14)", "width": "100%", "paddingX": "1.3em", "height": "2.5em"}}>
|
||||
<Image src={`/contact-icon.png`} sx={{"height": "2.5em", "padding": "0.5em"}}/>
|
||||
<Text>
|
||||
{`Kontakt`}
|
||||
</Text>
|
||||
</HStack>
|
||||
</Link>
|
||||
<Link as={NextLink} href={`/settings`} sx={{"width": "100%"}}>
|
||||
<HStack sx={{"bg": isTrue((state.router.page.path === "/zdj\\u0119cie") || (((state.router.page.path === "/") && "Zdj\\u0119cie") === "Home")) ? `#fff0f7` : `transparent`, "color": isTrue((state.router.page.path === "/zdj\\u0119cie") || (((state.router.page.path === "/") && "Zdj\\u0119cie") === "Home")) ? `#1A1060` : `black`, "borderRadius": "0.375rem", "boxShadow": "0px 0px 0px 1px rgba(84, 82, 95, 0.14)", "width": "100%", "paddingX": "1.3em", "height": "2.5em"}}>
|
||||
<Image src={`/image-icon.png`} sx={{"height": "2.5em", "padding": "0.5em"}}/>
|
||||
<Text>
|
||||
{`Zdjęcie`}
|
||||
</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( 64.5deg, rgba(245,116,185,1) 14.7%, rgba(89,97,223,1) 88.7% )", "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": "50px", "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>
|
||||
<VStack sx={{"marginRight": "40px", "marginLeft": "40px"}}>
|
||||
<Text sx={{"color": "rgba(117,102,253,255)", "fontSize": "2em", "fontWeight": "bold", "marginTop": "20px", "marginBottom": "5px"}}>
|
||||
{`Jak to działa?`}
|
||||
</Text>
|
||||
<List>
|
||||
<ListItem sx={{"marginRight": "50%"}}>
|
||||
<Text as={`b`} sx={{"fontSize": "1.2em"}}>
|
||||
{`1. Wgraj Zdjęcie `}
|
||||
</Text>
|
||||
<Text>
|
||||
{`Prosty proces rozpoczyna się od dodania zdjęcia, które chcesz anonimizować.`}
|
||||
</Text>
|
||||
</ListItem>
|
||||
<ListItem sx={{"marginLeft": "50%"}}>
|
||||
<Text as={`b`} sx={{"fontSize": "1.2em"}}>
|
||||
{`2. Zaznacz Twarz do Ochrony `}
|
||||
</Text>
|
||||
<Text>
|
||||
{`Oznacz twarz, którą chcesz zachować.`}
|
||||
</Text>
|
||||
</ListItem>
|
||||
<ListItem sx={{"marginRight": "50%"}}>
|
||||
<Text as={`b`} sx={{"fontSize": "1.2em"}}>
|
||||
{`3. Anonimizuj Resztę `}
|
||||
</Text>
|
||||
<Text>
|
||||
{`Naciśnij przycisk, a Blurme automatycznie zastosuje efekt rozmycia do wszystkich obszarów poza zaznaczoną twarzą.`}
|
||||
</Text>
|
||||
</ListItem>
|
||||
<ListItem sx={{"marginLeft": "50%"}}>
|
||||
<Text as={`b`} sx={{"fontSize": "1.2em"}}>
|
||||
{`4. Pobierz i Podziel Się `}
|
||||
</Text>
|
||||
<Text>
|
||||
{`Gotowe! Pobierz anonimizowane zdjęcie i dziel się nim bez obaw o prywatność.`}
|
||||
</Text>
|
||||
</ListItem>
|
||||
</List>
|
||||
</VStack>
|
||||
<VStack sx={{"textAlign": "center"}}>
|
||||
<Text sx={{"color": "rgba(251,108,174,255)", "fontSize": "2em", "fontWeight": "bold", "marginTop": "25px", "marginBottom": "5px"}}>
|
||||
{`Dlaczego Blurme?`}
|
||||
</Text>
|
||||
<Flex align={`top`}>
|
||||
<Box sx={{"marginLeft": "40px", "width": "315px", "padding": "5px"}}>
|
||||
<Text as={`b`} sx={{"fontSize": "1.3em"}}>
|
||||
{`Prywatność`}
|
||||
</Text>
|
||||
<Text sx={{"textAlign": "left"}}>
|
||||
{`Nasza aplikacja zapewnia pełną ochronę prywatności, eliminując ryzyko identyfikacji osób niezwiązanych z anonimizacją.`}
|
||||
</Text>
|
||||
</Box>
|
||||
<Box sx={{"width": "315px", "padding": "5px"}}>
|
||||
<Text as={`b`} sx={{"fontSize": "1.3em"}}>
|
||||
{`Elastyczność`}
|
||||
</Text>
|
||||
<Text sx={{"textAlign": "left"}}>
|
||||
{`Wybierz, która twarz na zdjęciu ma pozostać niezmieniona, a my zadbamy o resztę.`}
|
||||
</Text>
|
||||
</Box>
|
||||
<Box sx={{"marginRight": "40px", "width": "315px", "padding": "5px"}}>
|
||||
<Text as={`b`} sx={{"fontSize": "1.3em"}}>
|
||||
{`Prosta obsługa`}
|
||||
</Text>
|
||||
<Text sx={{"textAlign": "left"}}>
|
||||
{`Intuicyjny interfejs użytkownika sprawia, że korzystanie z Blurme jest łatwe dla każdego.`}
|
||||
</Text>
|
||||
</Box>
|
||||
</Flex>
|
||||
</VStack>
|
||||
<VStack sx={{"textAlign": "center"}}>
|
||||
<Text sx={{"color": "rgba(183,105,215,255)", "fontSize": "2em", "fontWeight": "bold", "marginTop": "25px", "marginBottom": "5px"}}>
|
||||
{`Do Czego?`}
|
||||
</Text>
|
||||
<List>
|
||||
<HStack sx={{"marginRight": "40px", "marginLeft": "40px"}}>
|
||||
<ListItem>
|
||||
<Text as={`b`} sx={{"fontSize": "1.3em"}}>
|
||||
{`Ochrona Tożsamości`}
|
||||
</Text>
|
||||
<Text sx={{"textAlign": "left"}}>
|
||||
{`Idealne do usuwania twarzy nieznajomych lub przypadkowych przechodniów z tła.`}
|
||||
</Text>
|
||||
</ListItem>
|
||||
<ListItem>
|
||||
<Text as={`b`} sx={{"fontSize": "1.3em"}}>
|
||||
{`Zachowanie Prywatności`}
|
||||
</Text>
|
||||
<Text sx={{"textAlign": "left"}}>
|
||||
{`Przydatne w sytuacjach, gdy chcesz udostępnić zdjęcia publicznie, ale z zachowaniem prywatności pewnych osób.`}
|
||||
</Text>
|
||||
</ListItem>
|
||||
<ListItem>
|
||||
<Text as={`b`} sx={{"fontSize": "1.3em"}}>
|
||||
{`Kreatywność`}
|
||||
</Text>
|
||||
<Text sx={{"textAlign": "left"}}>
|
||||
{`Wyraź swoją kreatywność, zachowując jednocześnie istotne elementy na zdjęciach.`}
|
||||
</Text>
|
||||
</ListItem>
|
||||
</HStack>
|
||||
</List>
|
||||
</VStack>
|
||||
</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%"}}>
|
||||
{`Kontakt`}
|
||||
</Link>
|
||||
</MenuItem>
|
||||
<MenuItem sx={{"_hover": {"bg": "#fff0f7"}}}>
|
||||
<Link as={NextLink} href={`/settings`} sx={{"width": "100%"}}>
|
||||
{`Zdjęcie`}
|
||||
</Link>
|
||||
</MenuItem>
|
||||
<MenuDivider/>
|
||||
<MenuItem sx={{"_hover": {"bg": "#fff0f7"}}}>
|
||||
<Link as={NextLink} href={`/about`} 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={`/home-icon.png`} property={`og:image`}/>
|
||||
<meta content={`width=device-width, shrink-to-fit=no, initial-scale=1`} name={`viewport`}/>
|
||||
</NextHead>
|
||||
</Fragment>
|
||||
)
|
||||
}
|
173
.web/pages/settings.js
Normal file
@ -0,0 +1,173 @@
|
||||
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": "100em"}}>
|
||||
<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": "1.3em", "height": "2.5em"}}>
|
||||
<Image src={`/home-icon.png`} sx={{"height": "2.5em", "padding": "0.5em"}}/>
|
||||
<Text>
|
||||
{`Strona główna`}
|
||||
</Text>
|
||||
</HStack>
|
||||
</Link>
|
||||
<Link as={NextLink} href={`/dashboard`} sx={{"width": "100%"}}>
|
||||
<HStack sx={{"bg": isTrue((state.router.page.path === "/kontakt") || (((state.router.page.path === "/") && "Kontakt") === "Home")) ? `#fff0f7` : `transparent`, "color": isTrue((state.router.page.path === "/kontakt") || (((state.router.page.path === "/") && "Kontakt") === "Home")) ? `#1A1060` : `black`, "borderRadius": "0.375rem", "boxShadow": "0px 0px 0px 1px rgba(84, 82, 95, 0.14)", "width": "100%", "paddingX": "1.3em", "height": "2.5em"}}>
|
||||
<Image src={`/contact-icon.png`} sx={{"height": "2.5em", "padding": "0.5em"}}/>
|
||||
<Text>
|
||||
{`Kontakt`}
|
||||
</Text>
|
||||
</HStack>
|
||||
</Link>
|
||||
<Link as={NextLink} href={`/settings`} sx={{"width": "100%"}}>
|
||||
<HStack sx={{"bg": isTrue((state.router.page.path === "/zdj\\u0119cie") || (((state.router.page.path === "/") && "Zdj\\u0119cie") === "Home")) ? `#fff0f7` : `transparent`, "color": isTrue((state.router.page.path === "/zdj\\u0119cie") || (((state.router.page.path === "/") && "Zdj\\u0119cie") === "Home")) ? `#1A1060` : `black`, "borderRadius": "0.375rem", "boxShadow": "0px 0px 0px 1px rgba(84, 82, 95, 0.14)", "width": "100%", "paddingX": "1.3em", "height": "2.5em"}}>
|
||||
<Image src={`/image-icon.png`} sx={{"height": "2.5em", "padding": "0.5em"}}/>
|
||||
<Text>
|
||||
{`Zdjęcie`}
|
||||
</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%"}}>
|
||||
{`Kontakt`}
|
||||
</Link>
|
||||
</MenuItem>
|
||||
<MenuItem sx={{"_hover": {"bg": "#fff0f7"}}}>
|
||||
<Link as={NextLink} href={`/settings`} sx={{"width": "100%"}}>
|
||||
{`Zdjęcie`}
|
||||
</Link>
|
||||
</MenuItem>
|
||||
<MenuDivider/>
|
||||
<MenuItem sx={{"_hover": {"bg": "#fff0f7"}}}>
|
||||
<Link as={NextLink} href={`/about`} 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>
|
||||
{`Zdjęcie`}
|
||||
</title>
|
||||
<meta content={`A Reflex app.`} name={`description`}/>
|
||||
<meta content={`/image-icon.png`} 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
@ -0,0 +1,6 @@
|
||||
module.exports = {
|
||||
plugins: {
|
||||
tailwindcss: {},
|
||||
autoprefixer: {},
|
||||
},
|
||||
}
|
BIN
.web/public/about-icon.png
Normal file
After Width: | Height: | Size: 761 B |
BIN
.web/public/contact-icon.png
Normal file
After Width: | Height: | Size: 529 B |
BIN
.web/public/favicon.ico
Normal file
After Width: | Height: | Size: 11 KiB |
10
.web/public/github.svg
Normal 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/home-icon.png
Normal file
After Width: | Height: | Size: 477 B |
BIN
.web/public/icon.png
Normal file
After Width: | Height: | Size: 95 KiB |
BIN
.web/public/image-icon.png
Normal file
After Width: | Height: | Size: 660 B |
BIN
.web/public/logo.png
Normal file
After Width: | Height: | Size: 93 KiB |
BIN
.web/public/obraz1.png
Normal file
After Width: | Height: | Size: 442 KiB |
13
.web/public/paneleft.svg
Normal 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
@ -0,0 +1 @@
|
||||
{"version": "0.3.2", "project_hash": 314788008789977327116213560167054871332}
|
1015
.web/styles/code/prism.js
Normal file
1
.web/styles/styles.css
Normal file
@ -0,0 +1 @@
|
||||
@import url('./tailwind.css');
|
3
.web/styles/tailwind.css
Normal file
@ -0,0 +1,3 @@
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
7
.web/tailwind.config.js
Normal file
@ -0,0 +1,7 @@
|
||||
/** @type {import('tailwindcss').Config} */
|
||||
module.exports = {
|
||||
content: ["./pages/**/*.{js,ts,jsx,tsx}"],
|
||||
theme: null,
|
||||
plugins: [
|
||||
],
|
||||
};
|
36
.web/utils/client_side_routing.js
Normal 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
@ -0,0 +1,5 @@
|
||||
import { memo } from "react"
|
||||
import { E, isTrue } from "/utils/state"
|
||||
|
||||
|
||||
|
30
.web/utils/context.js
Normal 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>
|
||||
)
|
||||
}
|
67
.web/utils/helpers/dataeditor.js
Normal 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
@ -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
@ -0,0 +1 @@
|
||||
export default {"styles": {"global": {"body": {}}}}
|
@ -1,7 +1,54 @@
|
||||
from blurme.components import about
|
||||
from blurme.templates import template
|
||||
|
||||
about_page = about.template(
|
||||
route="/about",
|
||||
title="About BlurMe",
|
||||
description="Learn more about the BlurMe app and its features.",
|
||||
)(about.about_content)
|
||||
import reflex as rx
|
||||
|
||||
about_authors_text = """
|
||||
# O Autorach
|
||||
|
||||
Witaj na stronie "O Autorach" aplikacji **BlurMe**! Jesteśmy zespół pasjonatów ze studiów, którzy zjednoczyli siły,
|
||||
aby stworzyć innowacyjne rozwiązanie mające na celu ochronę prywatności w erze współdzielonych zdjęć.
|
||||
|
||||
## Nasza Misja
|
||||
|
||||
Nasza grupa powstała z fascynacji technologią oraz z troską o prywatność w dzisiejszym świecie online. Świadomi wyzwań
|
||||
związanych z udostępnianiem fotografii publicznie, postanowiliśmy stworzyć aplikację, która umożliwi użytkownikom
|
||||
ochronę swojej prywatności, jednocześnie pozwalając na swobodne udostępnianie chwil z życia.
|
||||
|
||||
## Jak Powstała Nasza Grupa
|
||||
|
||||
Nasza grupa skupia się na różnorodnych dziedzinach, takich jak sztuczna inteligencja, przetwarzanie obrazów i
|
||||
programowanie. Nasza wspólna pasja do technologii oraz zaangażowanie w projekt zrodziły kreatywną współpracę.
|
||||
Spotkaliśmy się na naszych studiach, gdzie zauważyliśmy potrzebę stworzenia narzędzia, które połączy technologię z
|
||||
ochroną prywatności.
|
||||
|
||||
## Dlaczego "BlurMe"?
|
||||
|
||||
**BlurMe** to wynik naszej wspólnej wizji, aby uczynić proces ochrony prywatności prostym i dostępnym dla każdego.
|
||||
Chcemy, aby ludzie czuli się pewnie dzieląc się zdjęciami, nie martwiąc się o potencjalne naruszenia prywatności.
|
||||
|
||||
## O Naszych Umiejętnościach
|
||||
|
||||
Nasza grupa składa się z pasjonatów programowania, inżynierii oprogramowania i sztucznej inteligencji. Dzięki naszym
|
||||
umiejętnościom oraz współpracy zdobyliśmy doświadczenie w tworzeniu zaawansowanych algorytmów przetwarzania obrazów,
|
||||
które pozwalają na automatyczne wykrywanie twarzy i rejestracji samochodowych na zdjęciach.
|
||||
|
||||
## Kontakt
|
||||
|
||||
Jeśli masz pytania, sugestie lub chciałbyś dowiedzieć się więcej o naszej grupie, skontaktuj się z nami poprzez naszą
|
||||
stronę kontaktową. Jesteśmy otwarci na współpracę i ciekawe pomysły!
|
||||
|
||||
Dziękujemy za korzystanie z **BlurMe**!
|
||||
"""
|
||||
|
||||
|
||||
@template(route="/about", title="About BlurMe", description="Learn more about the BlurMe app and its features.")
|
||||
def about() -> rx.Component:
|
||||
"""The about page.
|
||||
|
||||
Returns:
|
||||
The UI for the about page.
|
||||
"""
|
||||
return rx.vstack(
|
||||
rx.heading("About Us", font_size="3em"),
|
||||
rx.markdown(about_authors_text)
|
||||
)
|
||||
|
@ -4,18 +4,25 @@ from blurme.templates import template
|
||||
import reflex as rx
|
||||
|
||||
|
||||
@template(route="/dashboard", title="Kontakt", image = "/contact-icon.png")
|
||||
@template(route="/dashboard", title="Kontakt", image="/contact-icon.png")
|
||||
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!"),
|
||||
return rx.fragment(
|
||||
rx.heading("Kontakt", font_size="3em"),
|
||||
rx.text("Witaj na stronie Kontaktowej w aplikacji BlurMe!"),
|
||||
rx.text(
|
||||
"You can edit this page in ",
|
||||
rx.code("{your_app}/pages/dashboard.py"),
|
||||
"Jesteśmy dostępni dla Ciebie na różnych platformach. "
|
||||
"Skontaktuj się z nami, gdy tylko masz pytania, sugestie lub "
|
||||
"po prostu chcesz porozmawiać o naszej aplikacji."
|
||||
),
|
||||
rx.heading("E-mail:", level=3),
|
||||
rx.text("Zapraszamy do napisania do nas na adres: contact@blurme.com"),
|
||||
rx.heading("Telefon:", level=3),
|
||||
rx.text("Możesz także zadzwonić pod numer: +48 123 456 789"),
|
||||
rx.heading("Media Społecznościowe:", level=3),
|
||||
rx.text("Odwiedź nas na Facebooku, Twitterze i Instagramie @BlurMeApp")
|
||||
)
|
||||
|
@ -1 +1,2 @@
|
||||
from .template import template
|
||||
|
||||
|