├── src ├── libs │ ├── __init__.py │ └── py_react.py ├── components │ ├── __init__.py │ ├── ui │ │ ├── __init__.py │ │ ├── header.py │ │ ├── spinner.py │ │ └── search.py │ └── charachters │ │ ├── __init__.py │ │ ├── char_grid.py │ │ └── char_item.py ├── img │ ├── bg.jpg │ ├── logo.png │ └── spinner.gif ├── static │ └── favicon.ico ├── index.py ├── index.html ├── app.py └── css │ └── app.css ├── .gitignore ├── README.md ├── parcel_patch.sh └── package.json /src/libs/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/components/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/components/ui/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/components/charachters/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/img/bg.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DanShai/react_in_python/HEAD/src/img/bg.jpg -------------------------------------------------------------------------------- /src/img/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DanShai/react_in_python/HEAD/src/img/logo.png -------------------------------------------------------------------------------- /src/img/spinner.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DanShai/react_in_python/HEAD/src/img/spinner.gif -------------------------------------------------------------------------------- /src/static/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DanShai/react_in_python/HEAD/src/static/favicon.ico -------------------------------------------------------------------------------- /src/index.py: -------------------------------------------------------------------------------- 1 | 2 | from libs.py_react import render 3 | from app import App 4 | 5 | render(App, None, 'root') 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | .vscode/ 3 | 4 | *.pyc 5 | *.bak 6 | *.log 7 | *.lock 8 | 9 | venv/ 10 | __target__/ 11 | dist/ 12 | dev/ 13 | .cache/ 14 | node_modules/ 15 | __pycache__/ 16 | 17 | -------------------------------------------------------------------------------- /src/components/ui/header.py: -------------------------------------------------------------------------------- 1 | from libs.py_react import createElement as CR 2 | from libs.py_react import require # __:skip 3 | logo = require('../img/logo.png') 4 | 5 | def Header(): 6 | h = CR('header' , {'className':'center'}, 7 | CR('img',{'src':logo,'alt':''},None) ) 8 | return h -------------------------------------------------------------------------------- /src/components/ui/spinner.py: -------------------------------------------------------------------------------- 1 | from libs.py_react import createElement as CR 2 | from libs.py_react import require # __:skip 3 | spnimg = require('../img/spinner.gif') 4 | 5 | 6 | def Spinner(): 7 | istyle = {{ 'width': '200px', 'margin': 'auto', 'display': 'block' }} 8 | return CR('img' , {'src':spnimg , 'alt':'loading','style':istyle}) 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Breaking Bad ! 2 | ## 100% python React app 3 | 4 | https://danshai.github.io/react_in_python/ 5 | 6 | ## the javascript version is at traversy media: 7 | https://github.com/bradtraversy/breaking-bad-cast 8 | 9 | # usage 10 | 1. yarn 11 | 2. yarn patch 12 | 3. yarn start 13 | 14 | # requirements 15 | - python 3.7.x 16 | - better use anaconda for your virtual env in terminal and vscode then : 17 | + conda create -n mytranscrypts python=3.7 18 | + conda activate mytranscrypts 19 | + pip install transcrypt 20 | -------------------------------------------------------------------------------- /src/components/charachters/char_grid.py: -------------------------------------------------------------------------------- 1 | from libs.py_react import createElement as CR 2 | from components.charachters.char_item import CharItem 3 | from components.ui.spinner import Spinner 4 | 5 | 6 | def CharGrid(props): 7 | items = props['items'] 8 | isLoading = props['isLoading'] 9 | get_item = lambda item : CR(CharItem, {'key': item['char_id'],'item': item} ) 10 | lst_items = list(map( get_item ,items)) 11 | section = CR('section',{'className':'cards'} , lst_items) 12 | 13 | return Spinner if isLoading else section -------------------------------------------------------------------------------- /parcel_patch.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | wget -P ./node_modules/parcel-bundler/src/ https://raw.githubusercontent.com/parcel-bundler/parcel/b1e6d59cc44489f20013fa3171e09788978d7aed/packages/core/parcel-bundler/src/Logger.js 4 | wget -P ./node_modules/parcel-bundler/src/utils/ https://raw.githubusercontent.com/parcel-bundler/parcel/b1e6d59cc44489f20013fa3171e09788978d7aed/packages/core/parcel-bundler/src/utils/prettyError.js 5 | wget -P ./node_modules/parcel-bundler/src/utils/ https://raw.githubusercontent.com/parcel-bundler/parcel/b1e6d59cc44489f20013fa3171e09788978d7aed/packages/core/parcel-bundler/src/utils/emoji.js 6 | -------------------------------------------------------------------------------- /src/components/ui/search.py: -------------------------------------------------------------------------------- 1 | from libs.py_react import useState, createElement as CR 2 | 3 | def Search(props): 4 | txt , setTxt = useState('') 5 | get_query = props['get_query'] 6 | 7 | def on_change(q): 8 | setTxt(q) 9 | get_query(q) 10 | 11 | q_inp_prp = {'type':'text' , 'className':'form-control', 12 | 'placeholder':'Search..','value':txt,'autoFocus':True, 13 | 'onChange': lambda e : on_change(e.target.value) } 14 | q_inp = CR('input' , q_inp_prp) 15 | form = CR('form',None,q_inp) 16 | section = CR('section',None,form) 17 | return section -------------------------------------------------------------------------------- /src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | React With Python 11 | 12 | 13 |
14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /src/components/charachters/char_item.py: -------------------------------------------------------------------------------- 1 | from libs.py_react import createElement as CR 2 | 3 | def CharItem(props): 4 | item = props['item'] 5 | li_nick = CR('li' , None , 'Nickname : ' + item['nickname']) 6 | li_status = CR('li' , None , 'Status : ' + item["status"]) 7 | ul = CR('ul', None , li_nick,li_status) 8 | h1 = CR('h1', None , item['name']) 9 | div_back = CR('div', {'className':'card-back'} ,h1,ul) 10 | img = CR('img', {'src': item['img'] ,'alt':'' } ) 11 | div_front = CR('div', {'className':'card-front'} ,img) 12 | div_inner = CR('div', {'className':'card-inner'} ,div_front,div_back) 13 | div_card = CR('div', {'className':'card'} ,div_inner) 14 | return div_card 15 | 16 | 17 | -------------------------------------------------------------------------------- /src/app.py: -------------------------------------------------------------------------------- 1 | from libs.py_react import useState,useEffect,axios, createElement as CR 2 | from components.ui.header import Header 3 | from components.ui.search import Search 4 | from components.charachters.char_grid import CharGrid 5 | 6 | 7 | def App(): 8 | items, setItems = useState([]) 9 | isLoading, setIsLoading = useState(True) 10 | query, setQuery = useState('') 11 | 12 | def get_chars(): 13 | async def fetch_chars(): 14 | setIsLoading(True) 15 | try: 16 | result = await axios('https://www.breakingbadapi.com/api/characters?name='+query) 17 | setItems(result.data) 18 | setIsLoading(False) 19 | except Exception as e: 20 | print(e) 21 | fetch_chars() 22 | 23 | useEffect( get_chars,[query]) 24 | get_query = lambda q : setQuery(q) 25 | header = CR(Header,None) 26 | search_q = CR(Search,{'get_query': get_query},None) 27 | char_grid = CR(CharGrid, {'isLoading':isLoading, 'items':items},None) 28 | div_container = CR('div' ,{'className':'container'} , 29 | header , search_q,char_grid) 30 | return div_container -------------------------------------------------------------------------------- /src/libs/py_react.py: -------------------------------------------------------------------------------- 1 | 2 | # dummy defs to shut up the python linter! 3 | # React.createElement( 4 | # type, 5 | # [props], 6 | # [...children] 7 | # ) 8 | 9 | # __pragma__ ('skip') 10 | 11 | class lib: 12 | def __call__(self,*args,**kwargs): 13 | pass 14 | 15 | @classmethod 16 | def createElement(cls,*args,**kwargs): 17 | pass 18 | @classmethod 19 | def render(cls,e,p): 20 | pass 21 | @classmethod 22 | def useState(cls,e): 23 | pass 24 | @classmethod 25 | def useEffect(cls,e,a): 26 | pass 27 | 28 | 29 | class document: 30 | @classmethod 31 | def getElementById(cls, x): 32 | pass 33 | @classmethod 34 | def addEventListener(cls, x,y): 35 | pass 36 | 37 | def require(l): 38 | return lib() 39 | 40 | # __pragma__ ('noskip') 41 | 42 | 43 | React = require('react') 44 | ReactDOM = require('react-dom') 45 | axios = require('axios') 46 | polyfill = require("@babel/polyfill") # required by async/await 47 | createElement = React.createElement 48 | useState = React.useState 49 | useEffect = React.useEffect 50 | 51 | # react render 52 | def render(current, props, root): 53 | def main(): 54 | ReactDOM.render( 55 | React.createElement(current, props), 56 | document.getElementById(root) 57 | ) 58 | 59 | document.addEventListener("DOMContentLoaded", main) 60 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react_in_python", 3 | "version": "1.0.0", 4 | "main": "index.js", 5 | "author": "danshai", 6 | "license": "MIT", 7 | "private": true, 8 | "homepage": "https://danshai.github.io/react_in_python", 9 | "parcel-plugin-python": { 10 | "command": "python -m transcrypt", 11 | "arguments": [ 12 | "--nomin", 13 | "--map", 14 | "-k", 15 | "-e", 16 | "--verbose" 17 | ] 18 | }, 19 | "scripts": { 20 | "start": "rm -rf dev; rm -rf src/__target__; NODE_ENV=development parcel --no-cache src/index.html --out-dir dev", 21 | "build": "rm -rf dist; NODE_ENV=production parcel build src/index.html --no-source-maps --out-dir dist --no-cache && cp dist/index.html dist/404.html", 22 | "predeploy": "rm -rf node_modules/gh-pages/.cache; NODE_ENV=production parcel build src/index.html --no-source-maps --out-dir dist --public-url '/react_in_python'", 23 | "deploy": "gh-pages -d dist", 24 | "patch": "./parcel_patch.sh", 25 | "test": "echo \"Error: no test specified\" && exit 1" 26 | }, 27 | "devDependencies": { 28 | "gh-pages": "^3.1.0", 29 | "parcel-bundler": "^1.12.5", 30 | "parcel-plugin-transcrypt": "^1.0.20" 31 | }, 32 | "dependencies": { 33 | "@babel/polyfill": "^7.12.1", 34 | "axios": "^0.21.1", 35 | "react": "^17.0.2", 36 | "react-dom": "^17.0.2", 37 | "react-icons": "^4.2.0", 38 | "react-number-format": "^4.5.3" 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/css/app.css: -------------------------------------------------------------------------------- 1 | * { 2 | box-sizing: border-box; 3 | margin: 0; 4 | padding: 0; 5 | } 6 | 7 | html, 8 | body { 9 | background: #000 url('../img/bg.jpg') no-repeat center center/cover; 10 | height: 100vh; 11 | color: #fff; 12 | font-family: Arial, Helvetica, sans-serif; 13 | } 14 | 15 | a { 16 | text-decoration: none; 17 | } 18 | 19 | .container { 20 | max-width: 1100px; 21 | margin: auto; 22 | padding: 0 20px; 23 | } 24 | 25 | .btn { 26 | display: inline-block; 27 | color: #fff; 28 | background-color: #3fb573; 29 | font-size: 1em; 30 | text-align: center; 31 | padding: 10px 15px; 32 | border: 0; 33 | margin: 10px 0; 34 | } 35 | 36 | header { 37 | height: 200px; 38 | } 39 | 40 | header img { 41 | width: 200px; 42 | } 43 | 44 | .center { 45 | display: flex; 46 | align-items: center; 47 | justify-content: center; 48 | } 49 | 50 | .search { 51 | height: 100px; 52 | } 53 | 54 | input[type='text'] { 55 | display: block; 56 | padding: 10px; 57 | font-size: 20px; 58 | border: 0; 59 | border-radius: 5px; 60 | width: 60%; 61 | margin: auto; 62 | outline: none; 63 | } 64 | 65 | .cards { 66 | display: grid; 67 | grid-template-columns: repeat(4, 1fr); 68 | gap: 1rem; 69 | } 70 | 71 | .card { 72 | cursor: pointer; 73 | background-color: transparent; 74 | height: 300px; 75 | perspective: 1000px; 76 | } 77 | 78 | .card h1 { 79 | font-size: 25px; 80 | border-bottom: 1px #fff solid; 81 | padding-bottom: 10px; 82 | margin-bottom: 10px; 83 | } 84 | 85 | .card img { 86 | width: 100%; 87 | height: 300px; 88 | object-fit: cover; 89 | } 90 | 91 | .card-inner { 92 | position: relative; 93 | width: 100%; 94 | height: 100%; 95 | transition: transform 0.8s; 96 | transform-style: preserve-3d; 97 | } 98 | 99 | .card:hover .card-inner { 100 | transform: rotateY(180deg); 101 | } 102 | 103 | .card-front, 104 | .card-back { 105 | position: absolute; 106 | width: 100%; 107 | height: 100%; 108 | -webkit-backface-visibility: hidden; 109 | backface-visibility: hidden; 110 | } 111 | 112 | .card-back { 113 | background-color: #333; 114 | color: white; 115 | padding: 20px; 116 | transform: rotateY(180deg); 117 | } 118 | 119 | .card li { 120 | list-style: none; 121 | padding-bottom: 10px; 122 | } 123 | 124 | @media (max-width: 800px) { 125 | .cards { 126 | grid-template-columns: repeat(2, 1fr); 127 | } 128 | } 129 | 130 | @media (max-width: 500px) { 131 | .cards { 132 | grid-template-columns: 1fr; 133 | } 134 | .card img { 135 | width: 100%; 136 | height: 300px; 137 | object-fit: contain; 138 | } 139 | } --------------------------------------------------------------------------------