Compare commits

..

No commits in common. "master" and "01fde3584446e27cfe1b688c8bb4ef3452958ba8" have entirely different histories.

80 changed files with 6447 additions and 589 deletions

2
.gitignore vendored
View File

@ -1,6 +1,4 @@
*.db
*.py[cod]
.idea/
.web
.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 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
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>
)
}

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

@ -0,0 +1,158 @@
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": "100%"}}>
<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") === "Strona g\\u0142\\u00f3wna")) ? `#d9a2ca` : `transparent`, "color": isTrue((state.router.page.path === "/strona g\\u0142\\u00f3wna") || (((state.router.page.path === "/") && "Strona g\\u0142\\u00f3wna") === "Strona g\\u0142\\u00f3wna")) ? `#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") === "Strona g\\u0142\\u00f3wna")) ? `#d9a2ca` : `transparent`, "color": isTrue((state.router.page.path === "/dashboard") || (((state.router.page.path === "/") && "Dashboard") === "Strona g\\u0142\\u00f3wna")) ? `#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") === "Strona g\\u0142\\u00f3wna")) ? `#d9a2ca` : `transparent`, "color": isTrue((state.router.page.path === "/settings") || (((state.router.page.path === "/") && "Settings") === "Strona g\\u0142\\u00f3wna")) ? `#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"}}>
{`BlurMe`}
</Heading>
<Text>
{`Dodaj zdjęcia, które chcesz zanonimizować!`}
</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": "#d9a2ca"}}}>
<Link as={NextLink} href={`/`} sx={{"width": "100%"}}>
{`Strona główna`}
</Link>
</MenuItem>
<MenuItem sx={{"_hover": {"bg": "#d9a2ca"}}}>
<Link as={NextLink} href={`/dashboard`} sx={{"width": "100%"}}>
{`Dashboard`}
</Link>
</MenuItem>
<MenuItem sx={{"_hover": {"bg": "#d9a2ca"}}}>
<Link as={NextLink} href={`/settings`} sx={{"width": "100%"}}>
{`Settings`}
</Link>
</MenuItem>
<MenuDivider/>
<MenuItem sx={{"_hover": {"bg": "#d9a2ca"}}}>
<Link as={NextLink} href={`https://github.com/reflex-dev`} sx={{"width": "100%"}}>
{`About`}
</Link>
</MenuItem>
<MenuItem sx={{"_hover": {"bg": "#d9a2ca"}}}>
<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>
)
}

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

@ -0,0 +1,250 @@
import { Fragment, useContext, useEffect, useRef, useState } from "react"
import { useRouter } from "next/router"
import { Event, getAllLocalStorageItems, getRefValue, getRefValues, isTrue, preventDefault, refs, spreadArraysOrObjects, uploadFiles, useEventLoop } from "/utils/state"
import { ColorModeContext, EventLoopContext, initialEvents, StateContext } from "/utils/context.js"
import "focus-visible/dist/focus-visible"
import { Box, Button, Heading, HStack, Image, Link, List, ListItem, Menu, MenuButton, MenuDivider, MenuItem, MenuList, Modal, ModalBody, ModalContent, ModalHeader, ModalOverlay, OrderedList, Spacer, Text, VStack, Wrap, WrapItem } 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": "100%"}}>
<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") === "Strona g\\u0142\\u00f3wna")) ? `#d9a2ca` : `transparent`, "color": isTrue((state.router.page.path === "/strona g\\u0142\\u00f3wna") || (((state.router.page.path === "/") && "Strona g\\u0142\\u00f3wna") === "Strona g\\u0142\\u00f3wna")) ? `#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") === "Strona g\\u0142\\u00f3wna")) ? `#d9a2ca` : `transparent`, "color": isTrue((state.router.page.path === "/dashboard") || (((state.router.page.path === "/") && "Dashboard") === "Strona g\\u0142\\u00f3wna")) ? `#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") === "Strona g\\u0142\\u00f3wna")) ? `#d9a2ca` : `transparent`, "color": isTrue((state.router.page.path === "/settings") || (((state.router.page.path === "/") && "Settings") === "Strona g\\u0142\\u00f3wna")) ? `#1A1060` : `black`, "borderRadius": "0.375rem", "boxShadow": "0px 0px 0px 1px rgba(84, 82, 95, 0.14)", "width": "100%", "paddingX": "1em", "height": "2em"}}>
<Text>
{`Settings`}
</Text>
</HStack>
</Link>
</VStack>
<Spacer/>
</VStack>
</Box>
<Box sx={{"paddingTop": "5em", "paddingX": ["auto", "2em"], "flex": "1"}}>
<Box sx={{"alignItems": "flex-start", "boxShadow": "0px 0px 0px 1px rgba(84, 82, 95, 0.14)", "borderRadius": "0.375rem", "padding": "1em", "marginBottom": "2em"}}>
<VStack>
<Heading sx={{"fontSize": "3em", "marginBottom": "25px", "backgroundImage": "linear-gradient(271.68deg, #7566fe 0.75%, #f96caf 88.52%)", "backgroundClip": "text", "padding": "10px"}}>
{`Witaj w BlurMe!`}
</Heading>
<Wrap>
<WrapItem sx={{"flex-direction": "column", "w": "45%"}}>
<Text sx={{"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>
</WrapItem>
<WrapItem>
<Text>
{`Tu będzie obrazek`}
</Text>
</WrapItem>
</Wrap>
<Link as={NextLink} href={`settings`} sx={{"color": "rgb(107,99,246)", "button": true, "padding": "50xp", "fontSize": "1.2em"}}>
<Button>
{`Zacznij anonimizować`}
</Button>
</Link>
<Text sx={{"fontSize": "1.5em", "fontWeight": "bold", "marginTop": "10px", "marginBottom": "10px"}}>
{`Dlaczego Blurme?`}
</Text>
<List sx={{"textAlign": "left"}}>
<ListItem>
<Text as={`b`}>
{`Prywatność: `}
</Text>
{`Nasza aplikacja zapewnia pełną ochronę prywatności, eliminując ryzyko identyfikacji osób niezwiązanych z anonimizacją.`}
</ListItem>
<ListItem>
<Text as={`b`}>
{`Prosta obsługa: `}
</Text>
{`Intuicyjny interfejs użytkownika sprawia, że korzystanie z Blurme jest łatwe dla każdego.`}
</ListItem>
<ListItem>
<Text as={`b`}>
{`Elastyczność: `}
</Text>
{`Wybierz, która twarz na zdjęciu ma pozostać niezmieniona, a my zadbamy o resztę. Indywidualizuj swoje zdjęcia zgodnie z własnymi potrzebami.`}
</ListItem>
</List>
<Text sx={{"fontSize": "1.5em", "fontWeight": "bold", "marginTop": "20px", "marginBottom": "10px"}}>
{`Jak to działa?`}
</Text>
<OrderedList>
<ListItem>
<Text as={`b`}>
{`Wgraj Zdjęcie: `}
</Text>
{`Prosty proces rozpoczyna się od dodania zdjęcia, które chcesz anonimizować.`}
</ListItem>
<ListItem>
<Text as={`b`}>
{`Zaznacz Twarz do Ochrony: `}
</Text>
{`Oznacz twarz, którą chcesz zachować.`}
</ListItem>
<ListItem>
<Text as={`b`}>
{`Anonimizuj Resztę: `}
</Text>
{`Naciśnij przycisk, a Blurme automatycznie zastosuje efekt rozmycia do wszystkich obszarów poza zaznaczoną twarzą.`}
</ListItem>
<ListItem>
<Text as={`b`}>
{`Pobierz i Podziel Się: `}
</Text>
{`Gotowe! Pobierz anonimizowane zdjęcie i dziel się nim bez obaw o prywatność.`}
</ListItem>
</OrderedList>
<Text sx={{"fontSize": "1.5em", "fontWeight": "bold", "marginTop": "20px", "marginBottom": "10px"}}>
{`Do Czego 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": "#d9a2ca"}}}>
<Link as={NextLink} href={`/`} sx={{"width": "100%"}}>
{`Strona główna`}
</Link>
</MenuItem>
<MenuItem sx={{"_hover": {"bg": "#d9a2ca"}}}>
<Link as={NextLink} href={`/dashboard`} sx={{"width": "100%"}}>
{`Dashboard`}
</Link>
</MenuItem>
<MenuItem sx={{"_hover": {"bg": "#d9a2ca"}}}>
<Link as={NextLink} href={`/settings`} sx={{"width": "100%"}}>
{`Settings`}
</Link>
</MenuItem>
<MenuDivider/>
<MenuItem sx={{"_hover": {"bg": "#d9a2ca"}}}>
<Link as={NextLink} href={`https://github.com/reflex-dev`} sx={{"width": "100%"}}>
{`About`}
</Link>
</MenuItem>
<MenuItem sx={{"_hover": {"bg": "#d9a2ca"}}}>
<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>
)
}

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

@ -0,0 +1,158 @@
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": "100%"}}>
<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") === "Strona g\\u0142\\u00f3wna")) ? `#d9a2ca` : `transparent`, "color": isTrue((state.router.page.path === "/strona g\\u0142\\u00f3wna") || (((state.router.page.path === "/") && "Strona g\\u0142\\u00f3wna") === "Strona g\\u0142\\u00f3wna")) ? `#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") === "Strona g\\u0142\\u00f3wna")) ? `#d9a2ca` : `transparent`, "color": isTrue((state.router.page.path === "/dashboard") || (((state.router.page.path === "/") && "Dashboard") === "Strona g\\u0142\\u00f3wna")) ? `#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") === "Strona g\\u0142\\u00f3wna")) ? `#d9a2ca` : `transparent`, "color": isTrue((state.router.page.path === "/settings") || (((state.router.page.path === "/") && "Settings") === "Strona g\\u0142\\u00f3wna")) ? `#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"}}>
{`BlurMe`}
</Heading>
<Text>
{`Dodaj zdjęcia, które chcesz zanonimizować!`}
</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": "#d9a2ca"}}}>
<Link as={NextLink} href={`/`} sx={{"width": "100%"}}>
{`Strona główna`}
</Link>
</MenuItem>
<MenuItem sx={{"_hover": {"bg": "#d9a2ca"}}}>
<Link as={NextLink} href={`/dashboard`} sx={{"width": "100%"}}>
{`Dashboard`}
</Link>
</MenuItem>
<MenuItem sx={{"_hover": {"bg": "#d9a2ca"}}}>
<Link as={NextLink} href={`/settings`} sx={{"width": "100%"}}>
{`Settings`}
</Link>
</MenuItem>
<MenuDivider/>
<MenuItem sx={{"_hover": {"bg": "#d9a2ca"}}}>
<Link as={NextLink} href={`https://github.com/reflex-dev`} sx={{"width": "100%"}}>
{`About`}
</Link>
</MenuItem>
<MenuItem sx={{"_hover": {"bg": "#d9a2ca"}}}>
<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": 99276045562630266666819420787377202120}

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,21 +1,27 @@
# BlurMe docs
# 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!
## Przygotowanie aplikacji po raz pierwszy
In this template, the base state handles the toggle for the sidebar.
```
apt install ffmeg libsm6 libxext6
pip install -r requirements.txt
reflex init
```
## Uruchamianie aplikacji (localhost:3000)
```
reflex run
```
## Troubleshooting
Jeżeli jest problem z uruchomieniem aplikacji, usunąć folder `.web` i ponownie wykonać `reflex init`, powinno wystarczyć.
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.

View File

@ -1,5 +0,0 @@
<application>
<component name="LafManager">
<laf class-name="com.intellij.ide.ui.laf.darcula.DarculaLaf" />
</component>
</application>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 903 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 761 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 107 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 732 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 529 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 126 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 159 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 598 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 477 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 98 KiB

After

Width:  |  Height:  |  Size: 95 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 820 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 660 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 112 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 760 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 155 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 235 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 51 KiB

View File

@ -8,6 +8,5 @@ from blurme.pages import *
import reflex as rx
# Create the app and compile it.
app = rx.App()
#style=styles.base_style
app = rx.App(style=styles.base_style)
app.compile()

View File

@ -2,31 +2,47 @@
from blurme import styles
from blurme.state import State
from reflex.page import get_decorated_pages
from blurme.templates import template
import reflex as rx
def sidebar_header() -> rx.Component:
"""Returns:
The sidebar header component.
"""
return rx.hstack(
# The logo.
rx.image(
src="/icon.png",
height="5em",
height="4.5em",
),
rx.spacer(),
width="100%",
border_bottom=styles.border,
padding="1em",
align = 'center',
)
def sidebar_item(text: str, icon: str, url: str) -> rx.Component:
#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:
@ -38,46 +54,38 @@ def sidebar_item(text: str, icon: str, url: str) -> rx.Component:
rx.Component: The sidebar item component.
"""
# Whether the item is active.
active = State.router.page.path == url
default_dark_icon = "/image-icon-dark.png"
#final_dark_icon = dark_icon if dark_icon else default_dark_icon
#active = (State.router.page.path == f"/{text.lower()}") | (
# (State.router.page.path == "/") & text == "Strona główna"
#)
active = (State.router.page.path == f"/{text.lower()}") | (
(State.router.page.path == "/") & text == "Strona główna"
)
return rx.link(
rx.hstack(
rx.image(
src=icon,
height="2.5em",
padding="0.5em",
),
#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,
#),
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="1.3em",
height = "2.5em"
padding_x="1em",
height = "2em"
),
href=url,
width="100%",
)
@ -87,6 +95,7 @@ def sidebar() -> rx.Component:
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(
@ -96,20 +105,19 @@ def sidebar() -> rx.Component:
*[
sidebar_item(
text=page.get("title", page["route"].strip("/").capitalize()),
icon=page.get("image",page["route"].strip("/").capitalize()),
#icon=page.get("image", "/github.svg"),
url=page["route"],
)
for page in get_decorated_pages()
],
rx.spacer(),
width="100%",
overflow_y="auto",
align_items="flex-start",
padding="1em",
),
rx.spacer(),
height="100em",
#sidebar_footer(),
height="100%",
),
display=["none", "none", "block"],
min_width=styles.sidebar_width,

View File

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

View File

@ -1,40 +0,0 @@
from blurme.templates import template
import reflex as rx
@template(route="/about", title="O autorach", image = "/about-icon.png")
def about() -> rx.Component:
return rx.vstack(rx.heading("O autorach", font_size="3em", margin_bottom="10px", background_image="linear-gradient( 64.5deg, rgba(245,116,185,1) 14.7%, rgba(89,97,223,1) 88.7% )", background_clip="text", padding = "10px"),
rx.box(
rx.text("Jesteśmy grupą studentek kierunku Przetwarzania i Analizy Danych, które postanowiły połączyć swoje różnorodne umiejętności w ramach wspólnego projektu. Zależy nam nie tylko na zdobywaniu wiedzy na uczelni, ale także na praktycznym wykorzystaniu naszych umiejętności w realnych projektach.", margin_right="5%", margin_left="5%", margin_bottom = "10px"),
rx.wrap(rx.hstack(
rx.vstack(rx.image(src= "/cooperation.png", height = "8em", align = "center"),
rx.text("Współpraca",as_="b",font_size="1.4em"),
rx.text("W naszym zespole każda z nas wnosi unikalne perspektywy i pomysły, co sprawia, że nasza współpraca jest inspirująca.", text_align="center"),),
rx.vstack(rx.image(src= "/innovation.png", height = "8em", align = "center"),
rx.text("Innowacyjność",as_="b",font_size="1.4em"),
rx.text("Naszym głównym celem jest stworzenie innowacyjnego rozwiązania, które nie tylko będzie spełniać potrzeby dzisiejszych czasów, ale również przyczyni się do ochrony prywatności w kontekście współdzielenia zdjęć online.", text_align="center"),),
rx.vstack(rx.image(src= "/data.png", height = "8em", align = "center"),
rx.text("Analiza danych", as_="b",font_size="1.4em"),
rx.text("Nasza pasja do analizy i zrozumienia dziedziny przetwarzania danych stanowią fundamenty naszego projektu.", text_align="center"),),
style={"align-self": "center", "align-items": "flex-start"}),style={"align-self": "center", "align-items": "flex-start"}, margin_right = "5%", margin_left = "5%"),
rx.text("Dzięki zaufaniu naszych użytkowników oraz naszemu zangażowaniu, wierzymy, że nasz projekt przyczyni się do tworzenia bezpieczniejszej przestrzeni dla współdzielenia zdjęć w sieci. Cieszymy się, że możemy być częścią tej fascynującej podróży!", margin_right="5%", margin_left="5%", margin_top="10px"), text_align = "center",),
rx.list(
rx.hstack(rx.list_item(rx.text("Nasza Misja",padding = "10px", text_align="center",background_image="linear-gradient( 64.5deg, rgba(245,116,185,1) 14.7%, rgba(89,97,223,1) 88.7% )", background_clip="text", font_size="2em", font_weight="bold",),
rx.text("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." ), margin_right = "30px"),
rx.image(src="/technology.png", height = "11em"), margin_top="30px", margin_bottom="5px"),
rx.hstack(rx.image(src="/about.png", height = "13em", margin_right = "30px"),
rx.list_item(rx.text("Jak Powstała Nasza Grupa",padding = "10px", text_align="center",background_image="linear-gradient( 64.5deg, rgba(245,116,185,1) 14.7%, rgba(89,97,223,1) 88.7% )", background_clip="text", font_size="2em", font_weight="bold", margin_top="30px", margin_bottom="5px"),
rx.text("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. 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.")),margin_top="30px", margin_bottom="5px"),
rx.hstack(rx.list_item(rx.text("Dlaczego BlurMe?",padding = "10px", text_align="center",background_image="linear-gradient( 64.5deg, rgba(245,116,185,1) 14.7%, rgba(89,97,223,1) 88.7% )", background_clip="text", font_size="2em", font_weight="bold"),
rx.text("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.",), margin_right = "30px"),
rx.image(src="/privacy.png", height = "11em"), margin_top="30px", margin_bottom="5px"),
text_align = "center"), margin_right= "65px", margin_left = "65px")
#rx.list_item(rx.text("Kontakt",padding = "10px", text_align="center",background_image="linear-gradient( 64.5deg, rgba(245,116,185,1) 14.7%, rgba(89,97,223,1) 88.7% )", background_clip="text", font_size="2em", font_weight="bold", margin_top="30px", margin_bottom="5px"),
# rx.text("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!"),margin_left = "40%"),
# text_align = "center"), margin_right= "65px", margin_left = "65px")

View File

@ -3,41 +3,15 @@ from blurme.templates import template
import reflex as rx
ICON_EMAIL = "assets/email-icon.png"
ICON_PHONE = "assets/phone-icon.png"
@template(route="/dashboard", title="Kontakt", image="/contact-icon.png")
@template(route="/dashboard", title="Dashboard")
def dashboard() -> rx.Component:
"""The dashboard page.
Returns:
The UI for the dashboard page.
"""
container_style = {"background-color": "#f5f5f5", "padding": "10px", "margin-bottom": "20px", "display": "flex", "align-items": "center"}
icon_style = {"width": "20px", "height": "30px", "margin-right": "10px"}
heading_style = {"font-weight": "bold", "font-family": "Arial, sans-serif"}
return rx.wrap(
rx.hstack(
rx.vstack(rx.hstack(rx.image(src="/sparkle-left.png",height="3em"),
rx.heading("Skontaktuj się!", font_size="2.6em", margin_bottom="10px", background_image="linear-gradient( 64.5deg, rgba(245,116,185,1) 14.7%, rgba(89,97,223,1) 88.7% )", background_clip="text", padding = "5px", text_align = "center"),
rx.image(src="/sparkle-right.png",height="3em"), style={"align-self": "center"}),
rx.text("Witaj na stronie Kontaktowej w aplikacji BlurMe!", font_size="1.2em", text_align = "center"),
rx.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.", margin_bottom="25px", text_align = "center"),
style={"align-self": "flex-start","margin-left": "50px", "margin-top": "50px", "align-self": "flex-start", "margin-bottom": "25px"}, width = "50%"),
rx.wrap(rx.vstack(rx.vstack(rx.fragment(
rx.hstack(rx.icon(tag="email",style=icon_style),
rx.heading("E-mail", level=3, style=heading_style), margin_top="20px"),
rx.text("Zapraszamy do napisania do nas na adres: contact@blurme.com"),
style=container_style,text_align = "center")),
rx.vstack(rx.fragment(
rx.hstack(rx.icon(tag="phone", style=icon_style),rx.heading("Telefon", level=3, style=heading_style), margin_top="20px"),
rx.text("Możesz także zadzwonić pod numer: +48 123 456 789"),
style=container_style, text_align = "center"),),
),style={"align-self": "flex-center"}),))
#return rx.vstack(
# rx.heading("BlurMe", font_size="3em"),
# rx.text("Dodaj zdjęcia, które chcesz zanonimizować!"),
#)

View File

@ -5,57 +5,34 @@ from blurme.templates import template
import reflex as rx
@template(route="/", title="Strona główna", image = "/home-icon.png")
@template(route="/", title="Strona główna", image="/github.svg")
def index() -> rx.Component:
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.wrap(
rx.hstack(
rx.vstack(rx.hstack(rx.image(src="sparkle-left.png",height="3em"),
rx.heading("Witaj w BlurMe!", font_size="2.6em", margin_bottom="10px", background_image="linear-gradient( 64.5deg, rgba(245,116,185,1) 14.7%, rgba(89,97,223,1) 88.7% )", background_clip="text", padding = "5px", text_align = "center"),
rx.image(src="sparkle-right.png",height="3em"), style={"align-self": "center"}),
rx.text("Usunięcie wybranych informacji identyfikujących ze zdjęć z wydarzeń i miejsc publicznych nigdy nie było tak proste!", font_size="1.2em", text_align = "center"),
rx.text("Dołącz do naszej społeczności użytkowników, którzy cieszą się swobodą dzielenia się chwilami bez obaw o bezpieczeństwo swoich danych osobowych. Zacznij tworzyć bezpieczne wspomnienia już dziś!", margin_bottom="25px", text_align = "center"),
rx.text("", pading = "25px"),
rx.link(rx.button("Zacznij anonimizować"), href="settings",align="center",color="rgba(117,102,254,255)",button=True,padding="50xp",font_size="1.2em"),
style={"align-self": "flex-start","margin-left": "50px", "margin-top": "50px", "align-self": "flex-start", "margin-bottom": "25px"}, width = "50%"),
rx.image(src="obraz1.png",height="25em",padding="0.5em",style={"margin-right": "60px"}),
style={"align-self": "flex-start"}, max_width='100%'),),
rx.text(" ", height = "20px"),
rx.vstack(rx.text("Jak to działa?", color = "rgba(117,102,253,255)", font_size="2em", font_weight="bold", margin_top="15px", margin_bottom="5px"),
rx.list(
rx.list_item(rx.text("1. Wgraj Zdjęcie ",as_="b",font_size="1.2em"),
rx.text("Prosty proces rozpoczyna się od dodania zdjęcia, które chcesz anonimizować.", ),margin_right = "50%"),
rx.list_item(rx.text("2. Zaznacz Twarz do Ochrony ",as_="b", font_size="1.2em"),
rx.text("Oznacz twarz, którą chcesz zachować.", ),margin_left = "50%"),
rx.list_item(rx.text("3. Anonimizuj Resztę ",as_="b", font_size="1.2em"),
rx.text("Naciśnij przycisk, a Blurme automatycznie zastosuje efekt rozmycia do wszystkich obszarów poza zaznaczoną twarzą.", ),margin_right ="50%"),
rx.list_item(rx.text("4. Pobierz i Podziel Się ",as_="b", font_size="1.2em"),
rx.text("Gotowe! Pobierz anonimizowane zdjęcie i dziel się nim bez obaw o prywatność.",), margin_left = "50%"),
), margin_right= "40px", margin_left = "40px"),
rx.vstack(rx.text("Dlaczego Blurme?", color = "rgba(251,108,174,255)", font_size="2em", font_weight="bold", margin_top="25px", margin_bottom="5px"),
rx.flex(
rx.box(rx.text("Prywatność",as_="b",font_size="1.3em"),
rx.text("Nasza aplikacja zapewnia pełną ochronę prywatności, eliminując ryzyko identyfikacji osób niezwiązanych z anonimizacją.", text_align="left",),margin_left= "40px", width="315px", padding = "5px"),
rx.box(rx.text("Elastyczność",as_="b",font_size="1.3em"),
rx.text("Wybierz, która twarz na zdjęciu ma pozostać niezmieniona, a my zadbamy o resztę.", text_align="left", ),width="315px",padding = "5px" ),
rx.box(rx.text("Prosta obsługa",as_="b",font_size="1.3em"),
rx.text("Intuicyjny interfejs użytkownika sprawia, że korzystanie z Blurme jest łatwe dla każdego.",text_align="left", ),margin_right= "40px", width="315px",padding = "5px"),
align = "top"), text_align = "center"),
rx.vstack(rx.text("Do Czego?", color = "rgba(183,105,215,255)", font_size="2em", font_weight="bold", margin_top="25px", margin_bottom="5px"),
rx.list(rx.hstack(
rx.list_item(rx.text("Ochrona Tożsamości",as_="b",font_size="1.3em"),
rx.text("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",font_size="1.3em"),
rx.text("Przydatne w sytuacjach, gdy 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",font_size="1.3em"),
rx.text("Wyraź swoją kreatywność, zachowując jednocześnie istotne elementy na zdjęciach.", text_align="left"),),
margin_right = "40px", margin_left = "40px"), ),text_align = "center", margin_bottom = "50px"),
# 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 = "left"),
)#Indywidualizuj swoje zdjęcia zgodnie z własnymi potrzebami
rx.wrap_item(rx.text("Aplikacja do anonimizacji zdjęć: usunięcie wybranych informacji identyfikujących ze zdjęć z wydarzeń i miejsc publicznych nigdy nie było tak proste!", font_size="1.2em"),
w="45%",style={"flex-direction": "column"}),
rx.wrap_item(rx.text("Tu będzie obrazek"),)
),
rx.link(rx.button("Zacznij anonimizować"),href= "settings",color="rgb(107,99,246)",button=True, padding = "50xp", font_size="1.2em"),
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("Prosta 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ć niezmieniona, a my zadbamy 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"),
)

View File

@ -1,106 +1,22 @@
"""The settings page."""
import asyncio
from blurme.templates import template
from blurme.state import State
import reflex as rx
color = "rgb(107,99,246)"
@template(route="/settings", title="Zdjęcie", image = "/image-icon.png")
@template(route="/settings", title="Settings")
def settings() -> rx.Component:
icon_style = {"width": "20px", "height": "30px", "margin-right": "10px"}
"""The settings page.
Returns:
The UI for the settings page.
"""
return rx.vstack(
rx.wrap(
rx.hstack(
rx.vstack(
rx.image(src="/brush-logo.png", height="5.2em", margin_bottom="15px"),
rx.heading("Anonimizuj już teraz!", font_size="2em", margin_bottom="15px",
background_image="linear-gradient(271.68deg, #7566fe 0.75%, #f96caf 88.52%)",
background_clip="text", padding="5px"),
rx.text("Dodaj zdjęcie, wybierz obszar i ukryj tożsamość: Anonimizacja zdjęć w mgnieniu oka szybko, łatwo, bezpłatnie!",
font_size="1.2em", text_align="center"), width="50%", margin_left="50px", margin_right="30px"),
rx.vstack(
rx.upload(
rx.vstack(
rx.image(src="/upload-icon.png", height="5em"),
rx.text("Przeciągnij, aby wybrać pliki", font_size="1em"),
rx.text("albo", font_size="0.8em"),
rx.button("Kliknij", font_size="1em", color="rgba(117,102,254,255)"),
rx.text(" "),
rx.text("Dostępne rozszerzenia plików: png, jpg, webp, tiff", font_size="0.8em"),
),
border=f"1px dotted {color}",
padding="1em",
width="120%",
max_width = "120%",
multiple=True,
margin_top="50px",
style={"align-self": "center"},
spacing="2em",
),
rx.foreach(rx.selected_files, rx.text),
rx.box(
rx.button("Załaduj zdjęcie",
on_click=lambda: State.handle_upload(rx.upload_files()),
color="rgba(47, 187, 74,255)"),
rx.alert_dialog(
rx.alert_dialog_overlay(
rx.alert_dialog_content(
rx.alert_dialog_header("Przekroczono limit plików"),
rx.alert_dialog_body("Nie można przesłać więcej niż 5 plików."),
rx.alert_dialog_footer(
rx.button("Zamknij", color="rgba(255, 75, 42,255)",
on_click=State.toggle_show(),
)
),
),
),
is_open=State.show,
),
rx.alert_dialog(
rx.alert_dialog_overlay(
rx.alert_dialog_content(
rx.alert_dialog_header("Nieprawidłowe rozszerzenie pliku"),
rx.alert_dialog_body("Dozwolone rozszerzenia plików to: png, jpg, webp, tiff."),
rx.alert_dialog_footer(
rx.button("Zamknij", color="rgba(255, 75, 42,255)",
on_click=State.toggle_extension_alert(),
)
),
),
),
is_open=State.show_extension_alert,
),
),
),
),
),
rx.responsive_grid(
rx.foreach(
State.img,
lambda img: rx.vstack(
rx.image(src=f'/{img}', max_height="450px"),
rx.text(img, font_size="15px"),
rx.hstack(
rx.button(rx.icon(tag="delete", margin_right="8px"), " Usuń",
on_click=lambda img_name=img: State.delete_image(img_name),
color="rgba(255, 75, 42,255)", width="100px"),
rx.button(rx.icon(tag="check", margin_right="8px"), " Wykrywaj",
on_click=lambda img_name=img: State.image_analysis(img_name),
width="130px"),
rx.button(rx.icon(tag="edit", margin_right="8px"), " Anonimizuj",
on_click=lambda img_name=img: State.image_anonymization(img_name),
width="130px"),
rx.button(rx.icon(tag="download", margin_right="8px"), " Pobierz",
on_click=lambda img_name=img: State.download_image(img_name),
width="100px"),
padding="1em"
),
),
),
columns=[1],
spacing="5px",
),
)
rx.heading("BlurMe", font_size="3em"),
rx.text("Dodaj zdjęcia, które chcesz zanonimizować!"),
)
# rx.text(
# "You can edit this page in ",
# rx.code("{your_app}/pages/settings.py"),
# ),

View File

@ -1,95 +1,12 @@
"""Base state for the app."""
import reflex as rx
import os
import stat
#from blurme import styles
import asyncio
from graphics.image_modification import blur_boxes_on_image
from graphics.image_modification import show_image_with_boxes
from ml.element_detection import BoundBox, detect
class State(rx.State):
"""The app state."""
"""Base state for the app.
# The images to show.
img: list[str] = []
show: bool = False
def toggle_show(self):
self.show = not self.show
The base state is used to store general vars used throughout the app.
"""
show_extension_alert: bool = False
def toggle_extension_alert(self):
self.show_extension_alert = not self.show_extension_alert
def change(self):
self.show = not (self.show)
async def handle_upload(self, files: list[rx.UploadFile]):
"""Handle the upload of file(s).
Args:
files: The uploaded files.
"""
max_files_limit = 5
acceptable_extensions = {"png", "jpg", "jpeg", "webp", "tif", "tiff"}
if len(self.img) + len(files) > max_files_limit:
self.toggle_show()
return
for file in files:
upload_data = await file.read()
outfile = rx.get_asset_path(file.filename)
extension = file.filename.split(".")[-1]
if extension.lower() not in acceptable_extensions:
self.toggle_extension_alert()
return
# Save the file.
with open(outfile, "wb") as file_object:
file_object.write(upload_data)
# Update the img var.
self.img.append(file.filename)
self.img = self.img
def delete_image(self, img_name: str):
if img_name in self.img:
self.img.remove(img_name)
self.img=self.img
def download_image(self, img_name):
print(self.img)
return rx.download(url=f'/{img_name}', filename=img_name)
async def image_analysis(self, img_name: str):
if img_name in self.img:
stripped_image_name = str(img_name).lstrip("analysis-").lstrip("anonim-")
image_path = rx.get_asset_path(stripped_image_name)
#timestamp = datetime.now().strftime("%Y%m%d%H%M%S%f")
new_image_name = f"analysis-{stripped_image_name}"
new_image_path = rx.get_asset_path(new_image_name)
show_image_with_boxes(image_path, detect(image_path), new_image_path)
upload_file = rx.UploadFile(file=open(new_image_path, 'rb'), filename=new_image_name)
self.delete_image(img_name)
await self.handle_upload([upload_file])
self.img=self.img
async def image_anonymization(self, img_name: str):
if img_name in self.img:
stripped_image_name = str(img_name).lstrip("analysis-").lstrip("anonim-")
image_path = rx.get_asset_path(stripped_image_name)
#timestamp = datetime.now().strftime("%Y%m%d%H%M%S%f")
new_image_name = f"anonim-{stripped_image_name}"
new_image_path = rx.get_asset_path(new_image_name)
blur_boxes_on_image(image_path, detect(image_path), new_image_path)
upload_file = rx.UploadFile(file=open(new_image_path, 'rb'), filename=new_image_name)
self.delete_image(img_name)
await self.handle_upload([upload_file])
self.img=self.img
pass

View File

@ -1,18 +1,13 @@
"""Styles for the app."""
import reflex as rx
from blurme.state import State
border_radius = "0.375rem"
box_shadow = "0px 0px 0px 1px rgba(84, 82, 95, 0.14)"
border = "1px solid #F4F3F6"
text_color = "black"
#dark_mode_state = State.toggle_dark_mode()
#text_color = "white" if dark_mode_state == True else "purple"
text_color_light = "black"
text_color_dark = "white"
accent_text_color = "black"
accent_color = "linear-gradient(linear-gradient(43deg, rgba(201,238,248,1) 18%, rgba(253,210,227,0.7792366946778712) 86%)"
accent_text_color = "#1A1060"
accent_color = "#d9a2ca"
hover_accent_color = {"_hover": {"color": accent_color}}
hover_accent_bg = {"_hover": {"bg": accent_color}}
content_width_vw = "90vw"
@ -40,6 +35,15 @@ overlapping_button_style = {
"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(

View File

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

View File

@ -5,7 +5,6 @@ from __future__ import annotations
from blurme import styles
from blurme.components.sidebar import sidebar
from typing import Callable
from blurme.state import State
import reflex as rx
@ -17,27 +16,55 @@ default_meta = [
},
]
def dark_mode() -> rx.Component:
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.button(
rx.menu(
rx.menu_button(
rx.icon(
tag="moon",
tag="hamburger",
size="4em",
color=styles.text_color,
),
on_click= rx.toggle_color_mode
#State.toggle_dark_mode()
),
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",)
z_index="500",
)
def template(
route: str | None = None,
title: str | None = None,
image: str | None = None,
image_dark: str | None = None,
description: str | None = None,
meta: str | None = None,
script_tags: list[rx.Component] | None = None,
@ -49,10 +76,9 @@ def template(
route: The route to reach the page.
title: The title of the page.
image: The favicon of the page.
image_dark: The dark mode favicon of the page.
description: The description of the page.
meta: Additional meta to add to the page.
on_load: The event handler(s) called when the page loads.
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:
@ -90,7 +116,7 @@ def template(
),
**styles.template_page_style,
),
dark_mode(),
menu_button(),
align_items="flex-start",
transition="left 0.5s, width 0.5s",
position="relative",

View File

@ -1 +0,0 @@
<code_scheme name="Default" version="173" />

View File

@ -1,5 +0,0 @@
<application>
<component name="EditorColorsManagerImpl">
<global_color_scheme name="_@user_Darcula" />
</component>
</application>

View File

@ -1,6 +0,0 @@
<application>
<component name="XDebuggerSettings">
<data-views />
<general />
</component>
</application>

View File

@ -1,6 +0,0 @@
<application>
<component name="DefaultFont">
<option name="FONT_SIZE" value="18" />
<option name="FONT_SCALE" value="1.5" />
</component>
</application>

View File

@ -1,7 +0,0 @@
<application>
<component name="FileTypeManager" version="17">
<extensionMap>
<mapping ext="tex" type="PLAIN_TEXT" />
</extensionMap>
</component>
</application>

Binary file not shown.

View File

@ -1,48 +0,0 @@
import logging
import os
from typing import List
from PIL import Image, ImageDraw, ImageFont, ImageFilter
from PIL.Image import composite
from ml.element_detection import BoundBox
DIR_PATH = os.path.dirname(os.path.realpath(__file__))
def show_image_with_boxes(
in_image_path: str, bounding_boxes: List[BoundBox], out_image_path: str = None
):
img = Image.open(in_image_path)
draw = ImageDraw.Draw(img)
font_path = DIR_PATH + "/assets/fonts/arial.ttf"
font = ImageFont.truetype(font_path, 25)
for i, box in enumerate(bounding_boxes):
draw.rectangle(box.get_params(), outline="red", width=2, fill=None)
draw.text((box.x1 + 5, box.y1 + 5), str(i + 1), fill="red", font=font)
if not out_image_path:
out_image_path = (
in_image_path.split(".")[0] + "_out." + in_image_path.split(".")[1]
)
img.save(out_image_path)
def blur_boxes_on_image(
in_image_path: str, bounding_boxes: List[BoundBox], out_image_path: str = None
):
img = Image.open(in_image_path)
blur_img = img.filter(ImageFilter.GaussianBlur(radius=13)) # to increase the blur, increase the radius
mask = Image.new("L", img.size, 255)
draw_mask = ImageDraw.Draw(mask)
for box in bounding_boxes:
if box.selected:
if box.object == "plate":
draw_mask.rectangle(box.get_params(), fill=0)
elif box.object == "face":
draw_mask.ellipse(box.get_params(), fill=0)
mask = mask.filter(ImageFilter.GaussianBlur(radius=3))
img = composite(img, blur_img, mask)
if not out_image_path:
out_image_path = (
in_image_path.split(".")[0] + "_blurred." + in_image_path.split(".")[1]
)
img.save(out_image_path)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

Binary file not shown.

View File

@ -1,56 +0,0 @@
import os
from typing import List, Tuple
import torch
from ultralytics import YOLO
DIR_PATH = os.path.dirname(os.path.realpath(__file__))
FACES_PATH = os.path.join(DIR_PATH, "assets/weights/yolov8n-face.pt")
PLATES_PATH = os.path.join(DIR_PATH, "assets/weights/yolov8-plate.pt")
FACES_MODEL = YOLO(FACES_PATH)
PLATES_MODEL = YOLO(PLATES_PATH)
CONF_THRESH = 0.3
IOU_THRESH = 0.5
class BoundBox:
def __init__(self, x1, y1, x2, y2, object=None):
self.x1, self.y1, self.x2, self.y2 = x1, y1, x2, y2
self.selected = True
if object not in ["face", "plate"]:
raise ValueError("object must be either 'face' or 'plate'")
self.object = object
def select(self):
self.selected = True
def unselect(self):
self.selected = False
def get_params(self) -> Tuple[int, int, int, int]:
return self.x1, self.y1, self.x2, self.y2
def detect(image_path: str) -> List[BoundBox]:
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
faces = FACES_MODEL.predict(
source=image_path, conf=CONF_THRESH, iou=IOU_THRESH, device=device
)
faces = faces[0].cpu().numpy().boxes
plates = PLATES_MODEL.predict(
source=image_path, conf=CONF_THRESH, iou=IOU_THRESH, device=device
)
plates = plates[0].cpu().numpy().boxes
bounding_boxes = []
for boxes, tag in zip([faces, plates], ["face", "plate"]):
for box in boxes:
xyxyn = box.xyxy[0]
x1 = int(xyxyn[0])
y1 = int(xyxyn[1])
x2 = int(xyxyn[2])
y2 = int(xyxyn[3])
bounding_boxes.append(BoundBox(x1, y1, x2, y2, tag))
return bounding_boxes

View File

@ -1,59 +0,0 @@
import unittest
from unittest.mock import Mock, patch
from element_detection import detect, BoundBox
from graphics.image_modification import show_image_with_boxes
class TestYourModule(unittest.TestCase):
@patch('element_detection.FACES_MODEL.predict')
@patch('element_detection.PLATES_MODEL.predict')
def test_detect(self, mock_plates_predict, mock_faces_predict):
# Przygotuj dane testowe
image_path = 'blurme/ml/MicrosoftTeams-image.png'
mock_faces_predict.__iter__.return_value = [Mock(cpu=Mock(return_value=Mock(numpy=Mock(boxes=[Mock(xyxy=[1, 2, 3, 4])]))))]
mock_plates_predict.__iter__.return_value = [Mock(cpu=Mock(return_value=Mock(numpy=Mock(boxes=[Mock(xyxy=[5, 6, 7, 8])]))))]
# Wywołaj funkcję
result = detect(image_path)
# Sprawdź, czy otrzymałeś oczekiwany wynik
expected_result = [BoundBox(1, 2, 3, 4), BoundBox(5, 6, 7, 8)]
self.assertEqual(result, expected_result)
@patch('element_detection.ImageDraw.Draw')
@patch('element_detection.ImageFont.truetype')
#@patch('element_detection.Image.save')
@patch('element_detection.Image.open')
def test_show_image_with_boxes(self, mock_image_open,
#mock_image_save,
mock_truetype, mock_draw):
# Przygotuj dane testowe
in_image_path = 'blurme/ml/MicrosoftTeams-image.png'
bounding_boxes = [BoundBox(1, 2, 3, 4), BoundBox(5, 6, 7, 8)]
# Wywołaj funkcję
show_image_with_boxes(in_image_path, bounding_boxes)
# Sprawdź, czy odpowiednie metody zostały wywołane z oczekiwanymi argumentami
mock_image_open.assert_called_once_with(in_image_path)
mock_draw.assert_called_once()
# mock_image_save.assert_called_once()
if __name__ == '__main__':
unittest.main()
def test_detect_with_stub(self, mock_plates_predict, mock_faces_predict):
# Przygotuj dane testowe
image_path = 'blurme/ml/MicrosoftTeams-image.png'
mock_faces_predict.__iter__.return_value = [Mock(cpu=Mock(return_value=Mock(numpy=Mock(boxes=[Mock(xyxy=[1, 2, 3, 4])]))))]
mock_plates_predict.__iter__.return_value = [Mock(cpu=Mock(return_value=Mock(numpy=Mock(boxes=[Mock(xyxy=[5, 6, 7, 8])]))))]
# Użyj stuba zamiast rzeczywistego BoundBox
with patch('element_detection.BoundBox', StubBoundBox):
# Wywołaj funkcję
result = detect(image_path)
# Sprawdź, czy otrzymałeś oczekiwany wynik
expected_result = [StubBoundBox(1, 2, 3, 4), StubBoundBox(5, 6, 7, 8)]
self.assertEqual(result, expected_result)

View File

@ -1,5 +1,2 @@
Pillow==10.1.0
reflex==0.3.2
torch==2.1.1
torchvision==0.16.1
ultralytics==8.0.212

View File

@ -1,5 +0,0 @@
<application>
<component name="UISettings">
<option name="FONT_SCALE" value="1.5" />
</component>
</application>