├── 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 | }
--------------------------------------------------------------------------------