new advanced example, small fixes

This commit is contained in:
Jonas Weinz 2023-06-03 12:21:28 +02:00
parent c51c1ba64a
commit 95c4319340
49 changed files with 116833 additions and 83349 deletions

View File

@ -1,3 +1,20 @@
# pyscript-bootstrap-templates # pyscript-bootstrap-templates
templates and pyscript wrappers for various bootstrap properties templates and pyscript wrappers for various bootstrap properties
## installation
```bash
pip install git+https://the-cake-is-a-lie.net/gogs/jonas/pyscript-bootstrap-templates.git
```
## usage
to create a new project, run
```bash
pyscript_bootstrap_app create <project_name> <title>
```
this will create a new folder containing a PWA app skeleton to start with.
The main entry point should be in main.py

View File

@ -8,7 +8,7 @@
"pyscript_py_url": "https://pyscript.net/latest/pyscript.py", "pyscript_py_url": "https://pyscript.net/latest/pyscript.py",
"bootstrap_css_url": "https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css", "bootstrap_css_url": "https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css",
"bootstrap_js_url": "https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/js/bootstrap.bundle.min.js", "bootstrap_js_url": "https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/js/bootstrap.bundle.min.js",
"pyscript_bootstrap_templates_wheel_url": "../dist/pyscript_bootstrap_templates-0.1.0-py3-none-any.whl", "pyscript_bootstrap_templates_wheel_url": "../dist/pyscript_bootstrap_templates-0.2.0-py3-none-any.whl",
"pwa_bg_color": "#000000", "pwa_bg_color": "#000000",
"pwa_theme_color": "#ffffff" "pwa_theme_color": "#ffffff"
} }

View File

@ -19,9 +19,11 @@
<body style="width: 100%; height: 100%"> <body style="width: 100%; height: 100%">
<py-config type="json"> <py-config type="json">
{ {
"autoclose_loader": true, "splashscreen": {
"autoclose": true
},
"packages": [ "packages": [
"./resources/pyscript_bootstrap_templates-0.1.0-py3-none-any.whl" "./resources/pyscript_bootstrap_templates-0.2.0-py3-none-any.whl"
], ],
"paths": [] "paths": []
} }

View File

@ -1,9 +1,35 @@
:not(:defined) { /* py-config - not a component */
display: none py-config {
display: none;
}
/* py-{el} - components not defined */
py-script:not(:defined) {
display: none;
} }
html{ py-repl:not(:defined) {
font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; display: none;
}
py-title:not(:defined) {
display: none;
}
py-inputbox:not(:defined) {
display: none;
}
py-button:not(:defined) {
display: none;
}
py-box:not(:defined) {
display: none;
}
html {
font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue',
Arial, 'Noto Sans', sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji';
line-height: 1.5; line-height: 1.5;
} }
@ -24,6 +50,7 @@ html{
border-right: 4px solid rgba(255, 255, 255, 0); border-right: 4px solid rgba(255, 255, 255, 0);
animation: spinner 0.6s linear infinite; animation: spinner 0.6s linear infinite;
} }
@keyframes spinner { @keyframes spinner {
to { to {
transform: rotate(360deg); transform: rotate(360deg);
@ -81,27 +108,68 @@ html{
right: 5%; right: 5%;
} }
.py-box{ /* Pop-up second layer end */
.alert-banner {
position: relative;
padding: 0.5rem 1.5rem 0.5rem 0.5rem;
margin: 5px 0;
}
.alert-banner p {
margin: 0;
}
.py-error {
background-color: #ffe9e8;
border: solid;
border-color: #f0625f;
color: #9d041c;
}
.py-warning {
background-color: rgb(255, 244, 229);
border: solid;
border-color: #ffa016;
color: #794700;
}
.alert-banner.py-error > #alert-close-button {
color: #9d041c;
}
.alert-banner.py-warning > #alert-close-button {
color: #794700;
}
#alert-close-button {
position: absolute;
right: 0.5rem;
top: 0.5rem;
cursor: pointer;
background: transparent;
border: none;
}
.py-box {
display: flex; display: flex;
flex-direction: row; flex-direction: row;
justify-content: flex-start; justify-content: flex-start;
} }
.py-box div.py-box-child * .py-box div.py-box-child * {
{
max-width: 100%; max-width: 100%;
} }
.py-repl-box{ .py-repl-box {
flex-direction: column; flex-direction: column;
} }
.editor-box{ .py-repl-editor {
--tw-border-opacity: 1; --tw-border-opacity: 1;
border-color: rgba(209, 213, 219, var(--tw-border-opacity)); border-color: rgba(209, 213, 219, var(--tw-border-opacity));
border-width: 1px; border-width: 1px;
position: relative; position: relative;
--tw-ring-inset: var(--tw-empty,/*!*/ /*!*/); --tw-ring-inset: var(--tw-empty, /*!*/ /*!*/);
--tw-ring-offset-width: 0px; --tw-ring-offset-width: 0px;
--tw-ring-offset-color: #fff; --tw-ring-offset-color: #fff;
--tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-color: rgba(59, 130, 246, 0.5);
@ -113,14 +181,14 @@ html{
box-sizing: border-box; box-sizing: border-box;
border-width: 1px; border-width: 1px;
border-style: solid; border-style: solid;
border-color: rgb(209, 213, 219) border-color: rgb(209, 213, 219);
} }
.editor-box:hover button{ .editor-box:hover button {
opacity: 1; opacity: 1;
} }
.repl-play-button{ .py-repl-run-button {
opacity: 0; opacity: 0;
bottom: 0.25rem; bottom: 0.25rem;
right: 0.25rem; right: 0.25rem;
@ -154,65 +222,64 @@ html{
border: 0; border: 0;
} }
.repl-play-button:hover{ .py-repl-run-button:hover {
opacity: 1; opacity: 1;
} }
.py-title{ .py-title {
text-transform: uppercase; text-transform: uppercase;
text-align: center; text-align: center;
} }
.py-title h1{ .py-title h1 {
font-weight: 700; font-weight: 700;
font-size: 1.875rem; font-size: 1.875rem;
} }
.py-input{ .py-input {
padding: 0.5rem; padding: 0.5rem;
--tw-border-opacity: 1; --tw-border-opacity: 1;
border-color: rgba(209, 213, 219, var(--tw-border-opacity)); border-color: rgba(209, 213, 219, var(--tw-border-opacity));
border-width: 1px; border-width: 1px;
border-radius: 0.25rem; border-radius: 0.25rem;
margin-right: 0.75rem; margin-right: 0.75rem;
border-style: solid; border-style: solid;
width: auto; width: auto;
} }
.py-box input.py-input{ .py-box input.py-input {
width: -webkit-fill-available; width: -webkit-fill-available;
} }
.central-content{ .central-content {
max-width: 20rem; max-width: 20rem;
margin-left: auto; margin-left: auto;
margin-right: auto; margin-right: auto;
} }
input { input {
text-rendering: auto; text-rendering: auto;
color: -internal-light-dark(black, white); color: -internal-light-dark(black, white);
letter-spacing: normal; letter-spacing: normal;
word-spacing: normal; word-spacing: normal;
line-height: normal; line-height: normal;
text-transform: none; text-transform: none;
text-indent: 0px; text-indent: 0px;
text-shadow: none; text-shadow: none;
display: inline-block; display: inline-block;
text-align: start; text-align: start;
appearance: auto; appearance: auto;
-webkit-rtl-ordering: logical; -webkit-rtl-ordering: logical;
cursor: text; background-color: -internal-light-dark(rgb(255, 255, 255), rgb(59, 59, 59));
background-color: -internal-light-dark(rgb(255, 255, 255), rgb(59, 59, 59)); margin: 0em;
margin: 0em; padding: 1px 2px;
padding: 1px 2px; border-width: 2px;
border-width: 2px; border-style: inset;
border-style: inset; border-color: -internal-light-dark(rgb(118, 118, 118), rgb(133, 133, 133));
border-color: -internal-light-dark(rgb(118, 118, 118), rgb(133, 133, 133)); border-image: initial;
border-image: initial;
} }
.py-button{ .py-button {
--tw-text-opacity: 1; --tw-text-opacity: 1;
color: rgba(255, 255, 255, var(--tw-text-opacity)); color: rgba(255, 255, 255, var(--tw-text-opacity));
padding: 0.5rem; padding: 0.5rem;
@ -222,17 +289,22 @@ input {
border-color: rgba(37, 99, 235, var(--tw-border-opacity)); border-color: rgba(37, 99, 235, var(--tw-border-opacity));
border-width: 1px; border-width: 1px;
border-radius: 0.25rem; border-radius: 0.25rem;
cursor: pointer;
} }
.py-li-element p{ .py-li-element p {
margin: 5px; margin: 5px;
} }
.py-li-element p{ .py-li-element p {
display: inline; display: inline;
} }
button, input, optgroup, select, textarea { button,
input,
optgroup,
select,
textarea {
font-family: inherit; font-family: inherit;
font-size: 100%; font-size: 100%;
line-height: 1.15; line-height: 1.15;
@ -242,3 +314,19 @@ button, input, optgroup, select, textarea {
.line-through { .line-through {
text-decoration: line-through; text-decoration: line-through;
} }
/* ===== py-terminal plugin ===== */
/* XXX: it would be nice if these rules were stored in e.g. pyterminal.css and
bundled together at build time (by rollup?) */
.py-terminal {
min-height: 10em;
background-color: black;
color: white;
padding: 0.5rem;
overflow: auto;
}
.py-terminal-hidden {
display: none;
}

File diff suppressed because one or more lines are too long

View File

@ -1,5 +1,5 @@
const pwa_version = "01_hello_world_202210161638" const pwa_version = "01_hello_world_202306031006"
const assets = ["./index.html", const assets = ["./index.html",
"./main.py", "./main.py",
"./resources/bootstrap.css", "./resources/bootstrap.css",
@ -7,7 +7,7 @@ const assets = ["./index.html",
"./resources/pyscript.css", "./resources/pyscript.css",
"./resources/pyscript.js", "./resources/pyscript.js",
"./resources/pyscript.py", "./resources/pyscript.py",
"./resources/pyscript_bootstrap_templates-0.1.0-py3-none-any.whl", "./resources/pyscript_bootstrap_templates-0.2.0-py3-none-any.whl",
"./resources/pwa.js", "./resources/pwa.js",
"./site.js", "./site.js",
] ]

View File

@ -11,7 +11,7 @@
"pyscript_py_url": "https://pyscript.net/latest/pyscript.py", "pyscript_py_url": "https://pyscript.net/latest/pyscript.py",
"bootstrap_css_url": "https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css", "bootstrap_css_url": "https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css",
"bootstrap_js_url": "https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/js/bootstrap.bundle.min.js", "bootstrap_js_url": "https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/js/bootstrap.bundle.min.js",
"pyscript_bootstrap_templates_wheel_url": "../dist/pyscript_bootstrap_templates-0.1.0-py3-none-any.whl", "pyscript_bootstrap_templates_wheel_url": "../dist/pyscript_bootstrap_templates-0.2.0-py3-none-any.whl",
"pwa_bg_color": "#000000", "pwa_bg_color": "#000000",
"pwa_theme_color": "#ffffff" "pwa_theme_color": "#ffffff"
} }

View File

@ -19,9 +19,11 @@
<body style="width: 100%; height: 100%"> <body style="width: 100%; height: 100%">
<py-config type="json"> <py-config type="json">
{ {
"autoclose_loader": true, "splashscreen": {
"autoclose": true
},
"packages": [ "packages": [
"./resources/pyscript_bootstrap_templates-0.1.0-py3-none-any.whl", "./resources/pyscript_bootstrap_templates-0.2.0-py3-none-any.whl",
"numpy", "numpy",
"scikit-image" "scikit-image"
], ],

View File

@ -1,9 +1,35 @@
:not(:defined) { /* py-config - not a component */
display: none py-config {
display: none;
}
/* py-{el} - components not defined */
py-script:not(:defined) {
display: none;
} }
html{ py-repl:not(:defined) {
font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; display: none;
}
py-title:not(:defined) {
display: none;
}
py-inputbox:not(:defined) {
display: none;
}
py-button:not(:defined) {
display: none;
}
py-box:not(:defined) {
display: none;
}
html {
font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue',
Arial, 'Noto Sans', sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji';
line-height: 1.5; line-height: 1.5;
} }
@ -24,6 +50,7 @@ html{
border-right: 4px solid rgba(255, 255, 255, 0); border-right: 4px solid rgba(255, 255, 255, 0);
animation: spinner 0.6s linear infinite; animation: spinner 0.6s linear infinite;
} }
@keyframes spinner { @keyframes spinner {
to { to {
transform: rotate(360deg); transform: rotate(360deg);
@ -81,27 +108,68 @@ html{
right: 5%; right: 5%;
} }
.py-box{ /* Pop-up second layer end */
.alert-banner {
position: relative;
padding: 0.5rem 1.5rem 0.5rem 0.5rem;
margin: 5px 0;
}
.alert-banner p {
margin: 0;
}
.py-error {
background-color: #ffe9e8;
border: solid;
border-color: #f0625f;
color: #9d041c;
}
.py-warning {
background-color: rgb(255, 244, 229);
border: solid;
border-color: #ffa016;
color: #794700;
}
.alert-banner.py-error > #alert-close-button {
color: #9d041c;
}
.alert-banner.py-warning > #alert-close-button {
color: #794700;
}
#alert-close-button {
position: absolute;
right: 0.5rem;
top: 0.5rem;
cursor: pointer;
background: transparent;
border: none;
}
.py-box {
display: flex; display: flex;
flex-direction: row; flex-direction: row;
justify-content: flex-start; justify-content: flex-start;
} }
.py-box div.py-box-child * .py-box div.py-box-child * {
{
max-width: 100%; max-width: 100%;
} }
.py-repl-box{ .py-repl-box {
flex-direction: column; flex-direction: column;
} }
.editor-box{ .py-repl-editor {
--tw-border-opacity: 1; --tw-border-opacity: 1;
border-color: rgba(209, 213, 219, var(--tw-border-opacity)); border-color: rgba(209, 213, 219, var(--tw-border-opacity));
border-width: 1px; border-width: 1px;
position: relative; position: relative;
--tw-ring-inset: var(--tw-empty,/*!*/ /*!*/); --tw-ring-inset: var(--tw-empty, /*!*/ /*!*/);
--tw-ring-offset-width: 0px; --tw-ring-offset-width: 0px;
--tw-ring-offset-color: #fff; --tw-ring-offset-color: #fff;
--tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-color: rgba(59, 130, 246, 0.5);
@ -113,14 +181,14 @@ html{
box-sizing: border-box; box-sizing: border-box;
border-width: 1px; border-width: 1px;
border-style: solid; border-style: solid;
border-color: rgb(209, 213, 219) border-color: rgb(209, 213, 219);
} }
.editor-box:hover button{ .editor-box:hover button {
opacity: 1; opacity: 1;
} }
.repl-play-button{ .py-repl-run-button {
opacity: 0; opacity: 0;
bottom: 0.25rem; bottom: 0.25rem;
right: 0.25rem; right: 0.25rem;
@ -154,65 +222,64 @@ html{
border: 0; border: 0;
} }
.repl-play-button:hover{ .py-repl-run-button:hover {
opacity: 1; opacity: 1;
} }
.py-title{ .py-title {
text-transform: uppercase; text-transform: uppercase;
text-align: center; text-align: center;
} }
.py-title h1{ .py-title h1 {
font-weight: 700; font-weight: 700;
font-size: 1.875rem; font-size: 1.875rem;
} }
.py-input{ .py-input {
padding: 0.5rem; padding: 0.5rem;
--tw-border-opacity: 1; --tw-border-opacity: 1;
border-color: rgba(209, 213, 219, var(--tw-border-opacity)); border-color: rgba(209, 213, 219, var(--tw-border-opacity));
border-width: 1px; border-width: 1px;
border-radius: 0.25rem; border-radius: 0.25rem;
margin-right: 0.75rem; margin-right: 0.75rem;
border-style: solid; border-style: solid;
width: auto; width: auto;
} }
.py-box input.py-input{ .py-box input.py-input {
width: -webkit-fill-available; width: -webkit-fill-available;
} }
.central-content{ .central-content {
max-width: 20rem; max-width: 20rem;
margin-left: auto; margin-left: auto;
margin-right: auto; margin-right: auto;
} }
input { input {
text-rendering: auto; text-rendering: auto;
color: -internal-light-dark(black, white); color: -internal-light-dark(black, white);
letter-spacing: normal; letter-spacing: normal;
word-spacing: normal; word-spacing: normal;
line-height: normal; line-height: normal;
text-transform: none; text-transform: none;
text-indent: 0px; text-indent: 0px;
text-shadow: none; text-shadow: none;
display: inline-block; display: inline-block;
text-align: start; text-align: start;
appearance: auto; appearance: auto;
-webkit-rtl-ordering: logical; -webkit-rtl-ordering: logical;
cursor: text; background-color: -internal-light-dark(rgb(255, 255, 255), rgb(59, 59, 59));
background-color: -internal-light-dark(rgb(255, 255, 255), rgb(59, 59, 59)); margin: 0em;
margin: 0em; padding: 1px 2px;
padding: 1px 2px; border-width: 2px;
border-width: 2px; border-style: inset;
border-style: inset; border-color: -internal-light-dark(rgb(118, 118, 118), rgb(133, 133, 133));
border-color: -internal-light-dark(rgb(118, 118, 118), rgb(133, 133, 133)); border-image: initial;
border-image: initial;
} }
.py-button{ .py-button {
--tw-text-opacity: 1; --tw-text-opacity: 1;
color: rgba(255, 255, 255, var(--tw-text-opacity)); color: rgba(255, 255, 255, var(--tw-text-opacity));
padding: 0.5rem; padding: 0.5rem;
@ -222,17 +289,22 @@ input {
border-color: rgba(37, 99, 235, var(--tw-border-opacity)); border-color: rgba(37, 99, 235, var(--tw-border-opacity));
border-width: 1px; border-width: 1px;
border-radius: 0.25rem; border-radius: 0.25rem;
cursor: pointer;
} }
.py-li-element p{ .py-li-element p {
margin: 5px; margin: 5px;
} }
.py-li-element p{ .py-li-element p {
display: inline; display: inline;
} }
button, input, optgroup, select, textarea { button,
input,
optgroup,
select,
textarea {
font-family: inherit; font-family: inherit;
font-size: 100%; font-size: 100%;
line-height: 1.15; line-height: 1.15;
@ -242,3 +314,19 @@ button, input, optgroup, select, textarea {
.line-through { .line-through {
text-decoration: line-through; text-decoration: line-through;
} }
/* ===== py-terminal plugin ===== */
/* XXX: it would be nice if these rules were stored in e.g. pyterminal.css and
bundled together at build time (by rollup?) */
.py-terminal {
min-height: 10em;
background-color: black;
color: white;
padding: 0.5rem;
overflow: auto;
}
.py-terminal-hidden {
display: none;
}

File diff suppressed because one or more lines are too long

View File

@ -1,5 +1,5 @@
const pwa_version = "02_image_filter_202210161638" const pwa_version = "02_image_filter_202306031006"
const assets = ["./index.html", const assets = ["./index.html",
"./main.py", "./main.py",
"./resources/bootstrap.css", "./resources/bootstrap.css",
@ -7,7 +7,7 @@ const assets = ["./index.html",
"./resources/pyscript.css", "./resources/pyscript.css",
"./resources/pyscript.js", "./resources/pyscript.js",
"./resources/pyscript.py", "./resources/pyscript.py",
"./resources/pyscript_bootstrap_templates-0.1.0-py3-none-any.whl", "./resources/pyscript_bootstrap_templates-0.2.0-py3-none-any.whl",
"./resources/pwa.js", "./resources/pwa.js",
"./site.js", "./site.js",
] ]

View File

@ -10,7 +10,7 @@
"pyscript_py_url": "https://pyscript.net/latest/pyscript.py", "pyscript_py_url": "https://pyscript.net/latest/pyscript.py",
"bootstrap_css_url": "https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css", "bootstrap_css_url": "https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css",
"bootstrap_js_url": "https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/js/bootstrap.bundle.min.js", "bootstrap_js_url": "https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/js/bootstrap.bundle.min.js",
"pyscript_bootstrap_templates_wheel_url": "../dist/pyscript_bootstrap_templates-0.1.0-py3-none-any.whl", "pyscript_bootstrap_templates_wheel_url": "../dist/pyscript_bootstrap_templates-0.2.0-py3-none-any.whl",
"pwa_bg_color": "#000000", "pwa_bg_color": "#000000",
"pwa_theme_color": "#ffffff" "pwa_theme_color": "#ffffff"
} }

View File

@ -19,9 +19,11 @@
<body style="width: 100%; height: 100%"> <body style="width: 100%; height: 100%">
<py-config type="json"> <py-config type="json">
{ {
"autoclose_loader": true, "splashscreen": {
"autoclose": true
},
"packages": [ "packages": [
"./resources/pyscript_bootstrap_templates-0.1.0-py3-none-any.whl", "./resources/pyscript_bootstrap_templates-0.2.0-py3-none-any.whl",
"numpy" "numpy"
], ],
"paths": [] "paths": []

View File

@ -1,9 +1,35 @@
:not(:defined) { /* py-config - not a component */
display: none py-config {
display: none;
}
/* py-{el} - components not defined */
py-script:not(:defined) {
display: none;
} }
html{ py-repl:not(:defined) {
font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; display: none;
}
py-title:not(:defined) {
display: none;
}
py-inputbox:not(:defined) {
display: none;
}
py-button:not(:defined) {
display: none;
}
py-box:not(:defined) {
display: none;
}
html {
font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue',
Arial, 'Noto Sans', sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji';
line-height: 1.5; line-height: 1.5;
} }
@ -24,6 +50,7 @@ html{
border-right: 4px solid rgba(255, 255, 255, 0); border-right: 4px solid rgba(255, 255, 255, 0);
animation: spinner 0.6s linear infinite; animation: spinner 0.6s linear infinite;
} }
@keyframes spinner { @keyframes spinner {
to { to {
transform: rotate(360deg); transform: rotate(360deg);
@ -81,27 +108,68 @@ html{
right: 5%; right: 5%;
} }
.py-box{ /* Pop-up second layer end */
.alert-banner {
position: relative;
padding: 0.5rem 1.5rem 0.5rem 0.5rem;
margin: 5px 0;
}
.alert-banner p {
margin: 0;
}
.py-error {
background-color: #ffe9e8;
border: solid;
border-color: #f0625f;
color: #9d041c;
}
.py-warning {
background-color: rgb(255, 244, 229);
border: solid;
border-color: #ffa016;
color: #794700;
}
.alert-banner.py-error > #alert-close-button {
color: #9d041c;
}
.alert-banner.py-warning > #alert-close-button {
color: #794700;
}
#alert-close-button {
position: absolute;
right: 0.5rem;
top: 0.5rem;
cursor: pointer;
background: transparent;
border: none;
}
.py-box {
display: flex; display: flex;
flex-direction: row; flex-direction: row;
justify-content: flex-start; justify-content: flex-start;
} }
.py-box div.py-box-child * .py-box div.py-box-child * {
{
max-width: 100%; max-width: 100%;
} }
.py-repl-box{ .py-repl-box {
flex-direction: column; flex-direction: column;
} }
.editor-box{ .py-repl-editor {
--tw-border-opacity: 1; --tw-border-opacity: 1;
border-color: rgba(209, 213, 219, var(--tw-border-opacity)); border-color: rgba(209, 213, 219, var(--tw-border-opacity));
border-width: 1px; border-width: 1px;
position: relative; position: relative;
--tw-ring-inset: var(--tw-empty,/*!*/ /*!*/); --tw-ring-inset: var(--tw-empty, /*!*/ /*!*/);
--tw-ring-offset-width: 0px; --tw-ring-offset-width: 0px;
--tw-ring-offset-color: #fff; --tw-ring-offset-color: #fff;
--tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-color: rgba(59, 130, 246, 0.5);
@ -113,14 +181,14 @@ html{
box-sizing: border-box; box-sizing: border-box;
border-width: 1px; border-width: 1px;
border-style: solid; border-style: solid;
border-color: rgb(209, 213, 219) border-color: rgb(209, 213, 219);
} }
.editor-box:hover button{ .editor-box:hover button {
opacity: 1; opacity: 1;
} }
.repl-play-button{ .py-repl-run-button {
opacity: 0; opacity: 0;
bottom: 0.25rem; bottom: 0.25rem;
right: 0.25rem; right: 0.25rem;
@ -154,65 +222,64 @@ html{
border: 0; border: 0;
} }
.repl-play-button:hover{ .py-repl-run-button:hover {
opacity: 1; opacity: 1;
} }
.py-title{ .py-title {
text-transform: uppercase; text-transform: uppercase;
text-align: center; text-align: center;
} }
.py-title h1{ .py-title h1 {
font-weight: 700; font-weight: 700;
font-size: 1.875rem; font-size: 1.875rem;
} }
.py-input{ .py-input {
padding: 0.5rem; padding: 0.5rem;
--tw-border-opacity: 1; --tw-border-opacity: 1;
border-color: rgba(209, 213, 219, var(--tw-border-opacity)); border-color: rgba(209, 213, 219, var(--tw-border-opacity));
border-width: 1px; border-width: 1px;
border-radius: 0.25rem; border-radius: 0.25rem;
margin-right: 0.75rem; margin-right: 0.75rem;
border-style: solid; border-style: solid;
width: auto; width: auto;
} }
.py-box input.py-input{ .py-box input.py-input {
width: -webkit-fill-available; width: -webkit-fill-available;
} }
.central-content{ .central-content {
max-width: 20rem; max-width: 20rem;
margin-left: auto; margin-left: auto;
margin-right: auto; margin-right: auto;
} }
input { input {
text-rendering: auto; text-rendering: auto;
color: -internal-light-dark(black, white); color: -internal-light-dark(black, white);
letter-spacing: normal; letter-spacing: normal;
word-spacing: normal; word-spacing: normal;
line-height: normal; line-height: normal;
text-transform: none; text-transform: none;
text-indent: 0px; text-indent: 0px;
text-shadow: none; text-shadow: none;
display: inline-block; display: inline-block;
text-align: start; text-align: start;
appearance: auto; appearance: auto;
-webkit-rtl-ordering: logical; -webkit-rtl-ordering: logical;
cursor: text; background-color: -internal-light-dark(rgb(255, 255, 255), rgb(59, 59, 59));
background-color: -internal-light-dark(rgb(255, 255, 255), rgb(59, 59, 59)); margin: 0em;
margin: 0em; padding: 1px 2px;
padding: 1px 2px; border-width: 2px;
border-width: 2px; border-style: inset;
border-style: inset; border-color: -internal-light-dark(rgb(118, 118, 118), rgb(133, 133, 133));
border-color: -internal-light-dark(rgb(118, 118, 118), rgb(133, 133, 133)); border-image: initial;
border-image: initial;
} }
.py-button{ .py-button {
--tw-text-opacity: 1; --tw-text-opacity: 1;
color: rgba(255, 255, 255, var(--tw-text-opacity)); color: rgba(255, 255, 255, var(--tw-text-opacity));
padding: 0.5rem; padding: 0.5rem;
@ -222,17 +289,22 @@ input {
border-color: rgba(37, 99, 235, var(--tw-border-opacity)); border-color: rgba(37, 99, 235, var(--tw-border-opacity));
border-width: 1px; border-width: 1px;
border-radius: 0.25rem; border-radius: 0.25rem;
cursor: pointer;
} }
.py-li-element p{ .py-li-element p {
margin: 5px; margin: 5px;
} }
.py-li-element p{ .py-li-element p {
display: inline; display: inline;
} }
button, input, optgroup, select, textarea { button,
input,
optgroup,
select,
textarea {
font-family: inherit; font-family: inherit;
font-size: 100%; font-size: 100%;
line-height: 1.15; line-height: 1.15;
@ -242,3 +314,19 @@ button, input, optgroup, select, textarea {
.line-through { .line-through {
text-decoration: line-through; text-decoration: line-through;
} }
/* ===== py-terminal plugin ===== */
/* XXX: it would be nice if these rules were stored in e.g. pyterminal.css and
bundled together at build time (by rollup?) */
.py-terminal {
min-height: 10em;
background-color: black;
color: white;
padding: 0.5rem;
overflow: auto;
}
.py-terminal-hidden {
display: none;
}

File diff suppressed because one or more lines are too long

View File

@ -1,5 +1,5 @@
const pwa_version = "03_numpy_grid_demo_202210161638" const pwa_version = "03_numpy_grid_demo_202306031006"
const assets = ["./index.html", const assets = ["./index.html",
"./main.py", "./main.py",
"./resources/bootstrap.css", "./resources/bootstrap.css",
@ -7,7 +7,7 @@ const assets = ["./index.html",
"./resources/pyscript.css", "./resources/pyscript.css",
"./resources/pyscript.js", "./resources/pyscript.js",
"./resources/pyscript.py", "./resources/pyscript.py",
"./resources/pyscript_bootstrap_templates-0.1.0-py3-none-any.whl", "./resources/pyscript_bootstrap_templates-0.2.0-py3-none-any.whl",
"./resources/pwa.js", "./resources/pwa.js",
"./site.js", "./site.js",
] ]

View File

@ -0,0 +1,18 @@
{
"root_folder": "04_cell_detector",
"title": "04_cell_detector",
"packages": [
"numpy",
"Pillow",
"opencv-python"
],
"paths": [],
"pyscript_css_url": "https://pyscript.net/latest/pyscript.css",
"pyscript_js_url": "https://pyscript.net/latest/pyscript.js",
"pyscript_py_url": "https://pyscript.net/latest/pyscript.py",
"bootstrap_css_url": "https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css",
"bootstrap_js_url": "https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/js/bootstrap.bundle.min.js",
"pyscript_bootstrap_templates_wheel_url": "../dist/pyscript_bootstrap_templates-0.2.0-py3-none-any.whl",
"pwa_bg_color": "#000000",
"pwa_theme_color": "#ffffff"
}

View File

@ -0,0 +1,40 @@
<!DOCTYPE html>
<html lang="en" style="width: 100%;height: 100%">
<head id="head">
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="manifest" href="manifest.json" />
<script src="./resources/pwa.js"></script>
<link rel="stylesheet" href="./resources/pyscript.css" />
<script defer src="./resources/pyscript.js"></script>
<!-- Bootstrap CSS -->
<link href="./resources/bootstrap.css" rel="stylesheet">
<title>04_cell_detector</title>
</head>
<body style="width: 100%; height: 100%">
<py-config type="json">
{
"splashscreen": {
"autoclose": true
},
"packages": [
"./resources/pyscript_bootstrap_templates-0.2.0-py3-none-any.whl",
"numpy",
"Pillow",
"opencv-python"
],
"paths": []
}
</py-config>
<div id="pyscript_app" style="height: 100%; min-height: 100%"></div>
<py-script src="./main.py"></py-script>
<script src="./resources/bootstrap.js"></script>
</body>
</html>

View File

@ -0,0 +1,214 @@
from pyscript_bootstrap_templates import bootstrap_templates
from pyscript_bootstrap_templates import bootstrap_HTML as bHTML
from pyscript_bootstrap_templates import bootstrap_inputs as bInputs
from pyscript_bootstrap_templates import HTML as HTML
import numpy as np
from PIL import Image
import cv2
loaded_img = None
app = bootstrap_templates.PyScriptBootstrapDashboard(parent_element="pyscript_app", brand_name="Pyscript Cell Detector")
main_div = bHTML.BootstrapContainer(parent=app.main)
main_div.w = 100
result_div = bHTML.BootstrapContainer(parent=main_div)
def process_image(image,
hough_min_dist = 500,
hough_param1 = 80,
hough_param2 = 500,
minRadius = 100,
maxRadius=500,
inner_hough_circles=True):
for child in result_div.children:
child.destroy()
#image = cv2.imread(str(test_img_path), cv2.IMREAD_COLOR)
# Convert the image to grayscale
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
# Use median blur to reduce noise
gray = cv2.medianBlur(gray, 5)
# Apply Hough transform on the image to find circles
circles = cv2.HoughCircles(gray,
cv2.HOUGH_GRADIENT,
2,
minDist=hough_min_dist,
param1=hough_param1,
param2=hough_param2,
minRadius=minRadius,
maxRadius=maxRadius)
if circles is None:
bHTML.AlertDanger("No cell areas detected in image", parent=app.main)
return False
# Convert to integers
circles = np.uint16(np.around(circles))
# Loop over all detected circles and add them to the image
#for i in circles[0,:]:
# # Draw outer circle
# cv2.circle(image,(i[0],i[1]),i[2],(0,255,0),2)
# # Draw center of circle
# cv2.circle(image,(i[0],i[1]),2,(0,0,255),3)
first_wells = []
first_wells_gray = []
for i in circles[0,:]:
center = (i[0], i[1])
radius = i[2]
# Cut out the well
well = image[center[1]-radius:center[1]+radius, center[0]-radius:center[0]+radius]
well_gray = gray[center[1]-radius:center[1]+radius, center[0]-radius:center[0]+radius]
first_wells.append(well)
first_wells_gray.append(well_gray)
if inner_hough_circles:
second_wells = []
second_wells_gray = []
for well, well_gray in zip(first_wells, first_wells_gray):
circles = cv2.HoughCircles(well_gray, cv2.HOUGH_GRADIENT, 1, minDist=hough_min_dist, param1=50, param2=50, minRadius=(well.shape[0] // 2) - 50, maxRadius=(well.shape[0] // 2) - 10)
if circles is not None:
circles = np.uint16(np.around(circles))
i = circles[0,:][0]
center = (i[0], i[1])
radius = i[2] - 1
min_y = max(center[1]-radius, 0)
max_y = center[1]+radius
min_x = max(center[0]-radius, 0)
max_x = center[0]+radius
second_wells.append(well[min_y:max_y, min_x:max_x])
second_wells_gray.append(well_gray[min_y:max_y, min_x:max_x])
else:
second_wells.append(well)
second_wells_gray.append(well_gray)
else:
second_wells = first_wells
second_wells_gray = first_wells_gray
for well, well_gray in zip(second_wells, second_wells_gray):
_, binary = cv2.threshold(255 - well_gray, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)
# Perform some morphological operations to remove small noise - you can change the kernel size
kernel = np.ones((3,3),np.uint8)
cleaned = cv2.morphologyEx(binary, cv2.MORPH_OPEN, kernel)
radius = well.shape[0] // 2 -1
# Now clear all pixels outside the circular well.
# We do this by creating a mask for the well and applying it to the image.
well_mask = np.zeros_like(cleaned, dtype=np.uint8)
cv2.circle(well_mask, (radius, radius), radius, 1, thickness=-1)
cleaned = cleaned * well_mask
circle = well_mask > 0
# now create an image overlay to display
ratio = np.sum((cleaned > 0).astype(int)) / np.sum(circle.astype(int))
div_result = bHTML.BootstrapContainer(f"ratio: {ratio * 100}%", parent=result_div)
div_result.shadow = bHTML.Shadow.LARGE
div_result.rounded = True
div_result.w = 50
well[cleaned > 0,0] = 255
final_img = bHTML.Image.from_numpy_array(well, parent=div_result)
final_img.rounded = True
final_img.rounded_size = 10
final_img.shadow = bHTML.Shadow.LARGE
final_img.w = 100
div = bHTML.BootstrapContainer("Controls", parent=app.sidebar)
div.font_size = 4
image_input = bInputs.InputFile(label_text="choose image file", parent=app.sidebar)
i_hough_min_dist = bInputs.InputInt("hough min distance [px]", parent=app.sidebar)
i_hough_min_dist.value = 500
i_hough_param1 = bInputs.InputInt("hough param1", parent=app.sidebar)
i_hough_param1.value = 80
i_hough_param2 = bInputs.InputInt("hough param2", parent=app.sidebar)
i_hough_param2.value = 500
i_hough_min_radius = bInputs.InputInt("hough min radius [px]", parent=app.sidebar)
i_hough_min_radius.value = 100
i_hough_max_radius = bInputs.InputInt("hough max radius [px]", parent=app.sidebar)
i_hough_max_radius.value = 500
btn = bHTML.ButtonPrimary("Process", parent=app.sidebar)
btn.w = 100
def on_image_change(f, *args):
global loaded_img
f.seek(0)
img = np.array(Image.open(f))
for child in result_div.children:
child.destroy()
output = bHTML.Image.from_numpy_array(img, parent=result_div)
output.rounded = True
output.rounded_size = 10
output.shadow = bHTML.Shadow.LARGE
output.w=50
loaded_img = img
image_input.onchange = on_image_change
def on_click(*args, **kwargs):
if loaded_img is None:
bHTML.AlertDanger("No image loaded", parent=app.main)
return
h_min_dist = int(i_hough_min_dist.value)
h_param1 = int(i_hough_param1.value)
h_param2 = int(i_hough_param2.value)
h_min_radius = int(i_hough_min_radius.value)
h_max_radius = int(i_hough_max_radius.value)
process_image(loaded_img,
hough_min_dist=h_min_dist,
hough_param1=h_param1,
hough_param2=h_param2,
minRadius=h_min_radius,
maxRadius=h_max_radius,
inner_hough_circles=True)
btn.onclick = on_click

View File

@ -0,0 +1 @@
{"name": "04_cell_detector", "short_name": "04_cell_detector", "start_url": "./index.html", "scope": ".", "display": "standalone", "background_color": "#000000", "theme_color": "#ffffff", "icons": [{"src": "resources/icon-512x512.png", "type": "image/png", "sizes": "512x512"}]}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

View File

@ -0,0 +1,10 @@
if ("serviceWorker" in navigator) {
window.addEventListener("load", function () {
navigator.serviceWorker
.register("./serviceWorker.js")
.then(res => console.log("service worker registered", res))
.catch(err => console.log("service worker not registered", err))
})
}

View File

@ -0,0 +1,332 @@
/* py-config - not a component */
py-config {
display: none;
}
/* py-{el} - components not defined */
py-script:not(:defined) {
display: none;
}
py-repl:not(:defined) {
display: none;
}
py-title:not(:defined) {
display: none;
}
py-inputbox:not(:defined) {
display: none;
}
py-button:not(:defined) {
display: none;
}
py-box:not(:defined) {
display: none;
}
html {
font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue',
Arial, 'Noto Sans', sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji';
line-height: 1.5;
}
.spinner::after {
content: '';
box-sizing: border-box;
width: 40px;
height: 40px;
position: absolute;
top: calc(40% - 20px);
left: calc(50% - 20px);
border-radius: 50%;
}
.spinner.smooth::after {
border-top: 4px solid rgba(255, 255, 255, 1);
border-left: 4px solid rgba(255, 255, 255, 1);
border-right: 4px solid rgba(255, 255, 255, 0);
animation: spinner 0.6s linear infinite;
}
@keyframes spinner {
to {
transform: rotate(360deg);
}
}
.label {
text-align: center;
width: 100%;
display: block;
color: rgba(255, 255, 255, 0.8);
font-size: 0.8rem;
margin-top: 6rem;
}
/* Pop-up second layer begin */
.py-overlay {
position: fixed;
display: flex;
justify-content: center;
align-items: center;
color: white;
top: 0;
bottom: 0;
left: 0;
right: 0;
background: rgba(0, 0, 0, 0.5);
transition: opacity 500ms;
visibility: hidden;
color: visible;
opacity: 1;
}
.py-overlay {
visibility: visible;
opacity: 1;
}
.py-pop-up {
text-align: center;
width: 600px;
}
.py-pop-up p {
margin: 5px;
}
.py-pop-up a {
position: absolute;
color: white;
text-decoration: none;
font-size: 200%;
top: 3.5%;
right: 5%;
}
/* Pop-up second layer end */
.alert-banner {
position: relative;
padding: 0.5rem 1.5rem 0.5rem 0.5rem;
margin: 5px 0;
}
.alert-banner p {
margin: 0;
}
.py-error {
background-color: #ffe9e8;
border: solid;
border-color: #f0625f;
color: #9d041c;
}
.py-warning {
background-color: rgb(255, 244, 229);
border: solid;
border-color: #ffa016;
color: #794700;
}
.alert-banner.py-error > #alert-close-button {
color: #9d041c;
}
.alert-banner.py-warning > #alert-close-button {
color: #794700;
}
#alert-close-button {
position: absolute;
right: 0.5rem;
top: 0.5rem;
cursor: pointer;
background: transparent;
border: none;
}
.py-box {
display: flex;
flex-direction: row;
justify-content: flex-start;
}
.py-box div.py-box-child * {
max-width: 100%;
}
.py-repl-box {
flex-direction: column;
}
.py-repl-editor {
--tw-border-opacity: 1;
border-color: rgba(209, 213, 219, var(--tw-border-opacity));
border-width: 1px;
position: relative;
--tw-ring-inset: var(--tw-empty, /*!*/ /*!*/);
--tw-ring-offset-width: 0px;
--tw-ring-offset-color: #fff;
--tw-ring-color: rgba(59, 130, 246, 0.5);
--tw-ring-offset-shadow: 0 0 #0000;
--tw-ring-shadow: 0 0 #0000;
--tw-shadow: 0 0 #0000;
position: relative;
box-sizing: border-box;
border-width: 1px;
border-style: solid;
border-color: rgb(209, 213, 219);
}
.editor-box:hover button {
opacity: 1;
}
.py-repl-run-button {
opacity: 0;
bottom: 0.25rem;
right: 0.25rem;
position: absolute;
padding: 0;
line-height: inherit;
color: inherit;
cursor: pointer;
background-color: transparent;
background-image: none;
-webkit-appearance: button;
text-transform: none;
font-family: inherit;
font-size: 100%;
margin: 0;
text-rendering: auto;
letter-spacing: normal;
word-spacing: normal;
line-height: normal;
text-transform: none;
text-indent: 0px;
text-shadow: none;
display: inline-block;
text-align: center;
align-items: flex-start;
cursor: default;
box-sizing: border-box;
background-color: -internal-light-dark(rgb(239, 239, 239), rgb(59, 59, 59));
margin: 0em;
padding: 1px 6px;
border: 0;
}
.py-repl-run-button:hover {
opacity: 1;
}
.py-title {
text-transform: uppercase;
text-align: center;
}
.py-title h1 {
font-weight: 700;
font-size: 1.875rem;
}
.py-input {
padding: 0.5rem;
--tw-border-opacity: 1;
border-color: rgba(209, 213, 219, var(--tw-border-opacity));
border-width: 1px;
border-radius: 0.25rem;
margin-right: 0.75rem;
border-style: solid;
width: auto;
}
.py-box input.py-input {
width: -webkit-fill-available;
}
.central-content {
max-width: 20rem;
margin-left: auto;
margin-right: auto;
}
input {
text-rendering: auto;
color: -internal-light-dark(black, white);
letter-spacing: normal;
word-spacing: normal;
line-height: normal;
text-transform: none;
text-indent: 0px;
text-shadow: none;
display: inline-block;
text-align: start;
appearance: auto;
-webkit-rtl-ordering: logical;
background-color: -internal-light-dark(rgb(255, 255, 255), rgb(59, 59, 59));
margin: 0em;
padding: 1px 2px;
border-width: 2px;
border-style: inset;
border-color: -internal-light-dark(rgb(118, 118, 118), rgb(133, 133, 133));
border-image: initial;
}
.py-button {
--tw-text-opacity: 1;
color: rgba(255, 255, 255, var(--tw-text-opacity));
padding: 0.5rem;
--tw-bg-opacity: 1;
background-color: rgba(37, 99, 235, var(--tw-bg-opacity));
--tw-border-opacity: 1;
border-color: rgba(37, 99, 235, var(--tw-border-opacity));
border-width: 1px;
border-radius: 0.25rem;
cursor: pointer;
}
.py-li-element p {
margin: 5px;
}
.py-li-element p {
display: inline;
}
button,
input,
optgroup,
select,
textarea {
font-family: inherit;
font-size: 100%;
line-height: 1.15;
margin: 0;
}
.line-through {
text-decoration: line-through;
}
/* ===== py-terminal plugin ===== */
/* XXX: it would be nice if these rules were stored in e.g. pyterminal.css and
bundled together at build time (by rollup?) */
.py-terminal {
min-height: 10em;
background-color: black;
color: white;
padding: 0.5rem;
overflow: auto;
}
.py-terminal-hidden {
display: none;
}

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,424 @@
import asyncio
import base64
import io
import sys
import time
from textwrap import dedent
import micropip # noqa: F401
from js import console, document
loop = asyncio.get_event_loop()
MIME_METHODS = {
"__repr__": "text/plain",
"_repr_html_": "text/html",
"_repr_markdown_": "text/markdown",
"_repr_svg_": "image/svg+xml",
"_repr_png_": "image/png",
"_repr_pdf_": "application/pdf",
"_repr_jpeg_": "image/jpeg",
"_repr_latex": "text/latex",
"_repr_json_": "application/json",
"_repr_javascript_": "application/javascript",
"savefig": "image/png",
}
def render_image(mime, value, meta):
data = f"data:{mime};charset=utf-8;base64,{value}"
attrs = " ".join(['{k}="{v}"' for k, v in meta.items()])
return f'<img src="{data}" {attrs}</img>'
def identity(value, meta):
return value
MIME_RENDERERS = {
"text/plain": identity,
"text/html": identity,
"image/png": lambda value, meta: render_image("image/png", value, meta),
"image/jpeg": lambda value, meta: render_image("image/jpeg", value, meta),
"image/svg+xml": identity,
"application/json": identity,
"application/javascript": lambda value, meta: f"<script>{value}</script>",
}
def eval_formatter(obj, print_method):
"""
Evaluates a formatter method.
"""
if print_method == "__repr__":
return repr(obj)
elif hasattr(obj, print_method):
if print_method == "savefig":
buf = io.BytesIO()
obj.savefig(buf, format="png")
buf.seek(0)
return base64.b64encode(buf.read()).decode("utf-8")
return getattr(obj, print_method)()
elif print_method == "_repr_mimebundle_":
return {}, {}
return None
def format_mime(obj):
"""
Formats object using _repr_x_ methods.
"""
if isinstance(obj, str):
return obj, "text/plain"
mimebundle = eval_formatter(obj, "_repr_mimebundle_")
if isinstance(mimebundle, tuple):
format_dict, _ = mimebundle
else:
format_dict = mimebundle
output, not_available = None, []
for method, mime_type in reversed(MIME_METHODS.items()):
if mime_type in format_dict:
output = format_dict[mime_type]
else:
output = eval_formatter(obj, method)
if output is None:
continue
elif mime_type not in MIME_RENDERERS:
not_available.append(mime_type)
continue
break
if output is None:
if not_available:
console.warning(
f"Rendered object requested unavailable MIME renderers: {not_available}"
)
output = repr(output)
mime_type = "text/plain"
elif isinstance(output, tuple):
output, meta = output
else:
meta = {}
return MIME_RENDERERS[mime_type](output, meta), mime_type
class PyScript:
loop = loop
@staticmethod
def write(element_id, value, append=False, exec_id=0):
"""Writes value to the element with id "element_id"""
console.log(f"APPENDING: {append} ==> {element_id} --> {value}")
if append:
child = document.createElement("div")
element = document.querySelector(f"#{element_id}")
if not element:
return
exec_id = exec_id or element.childElementCount + 1
element_id = child.id = f"{element_id}-{exec_id}"
element.appendChild(child)
element = document.getElementById(element_id)
html, mime_type = format_mime(value)
if mime_type in ("application/javascript", "text/html"):
script_element = document.createRange().createContextualFragment(html)
element.appendChild(script_element)
else:
element.innerHTML = html
@staticmethod
def run_until_complete(f):
_ = loop.run_until_complete(f)
class Element:
def __init__(self, element_id, element=None):
self._id = element_id
self._element = element
@property
def id(self):
return self._id
@property
def element(self):
"""Return the dom element"""
if not self._element:
self._element = document.querySelector(f"#{self._id}")
return self._element
@property
def value(self):
return self.element.value
@property
def innerHtml(self):
return self.element.innerHtml
def write(self, value, append=False):
console.log(f"Element.write: {value} --> {append}")
# TODO: it should be the opposite... pyscript.write should use the Element.write
# so we can consolidate on how we write depending on the element type
pyscript.write(self._id, value, append=append)
def clear(self):
if hasattr(self.element, "value"):
self.element.value = ""
else:
self.write("", append=False)
def select(self, query, from_content=False):
el = self.element
if from_content:
el = el.content
_el = el.querySelector(query)
if _el:
return Element(_el.id, _el)
else:
console.log(f"WARNING: can't find element matching query {query}")
def clone(self, new_id=None, to=None):
if new_id is None:
new_id = self.element.id
clone = self.element.cloneNode(True)
clone.id = new_id
if to:
to.element.appendChild(clone)
# Inject it into the DOM
self.element.after(clone)
return Element(clone.id, clone)
def remove_class(self, classname):
if isinstance(classname, list):
for cl in classname:
self.remove_class(cl)
else:
self.element.classList.remove(classname)
def add_class(self, classname):
self.element.classList.add(classname)
def add_classes(element, class_list):
for klass in class_list.split(" "):
element.classList.add(klass)
def create(what, id_=None, classes=""):
element = document.createElement(what)
if id_:
element.id = id_
add_classes(element, classes)
return Element(id_, element)
class PyWidgetTheme:
def __init__(self, main_style_classes):
self.main_style_classes = main_style_classes
def theme_it(self, widget):
for klass in self.main_style_classes.split(" "):
widget.classList.add(klass)
class PyItemTemplate(Element):
label_fields = None
def __init__(self, data, labels=None, state_key=None, parent=None):
self.data = data
self.register_parent(parent)
if not labels:
labels = list(self.data.keys())
self.labels = labels
self.state_key = state_key
super().__init__(self._id)
def register_parent(self, parent):
self._parent = parent
if parent:
self._id = f"{self._parent._id}-c-{len(self._parent._children)}"
self.data["id"] = self._id
else:
self._id = None
def create(self):
console.log("creating section")
new_child = create("section", self._id, "task bg-white my-1")
console.log("creating values")
console.log("creating innerHtml")
new_child._element.innerHTML = dedent(
f"""
<label for="flex items-center p-2 ">
<input class="mr-2" type="checkbox" class="task-check">
<p class="m-0 inline">{self.render_content()}</p>
</label>
"""
)
console.log("returning")
return new_child
def on_click(self, evt):
pass
def pre_append(self):
pass
def post_append(self):
self.element.click = self.on_click
self.element.onclick = self.on_click
self._post_append()
def _post_append(self):
pass
def strike(self, value, extra=None):
if value:
self.add_class("line-through")
else:
self.remove_class("line-through")
def render_content(self):
return " - ".join([self.data[f] for f in self.labels])
class PyListTemplate:
theme = PyWidgetTheme("flex flex-col-reverse mt-8 mx-8")
item_class = PyItemTemplate
def __init__(self, parent):
self.parent = parent
self._children = []
self._id = self.parent.id
@property
def children(self):
return self._children
@property
def data(self):
return [c.data for c in self._children]
def render_children(self):
binds = {}
for i, c in enumerate(self._children):
txt = c.element.innerHTML
rnd = str(time.time()).replace(".", "")[-5:]
new_id = f"{c.element.id}-{i}-{rnd}"
binds[new_id] = c.element.id
txt = txt.replace(">", f" id='{new_id}'>")
print(txt)
def foo(evt):
console.log(evt)
evtEl = evt.srcElement
srcEl = Element(binds[evtEl.id])
srcEl.element.onclick()
evtEl.classList = srcEl.element.classList
for new_id in binds:
Element(new_id).element.onclick = foo
def connect(self):
self.md = main_div = document.createElement("div")
main_div.id = self._id + "-list-tasks-container"
if self.theme:
self.theme.theme_it(main_div)
self.parent.appendChild(main_div)
def add(self, *args, **kws):
if not isinstance(args[0], self.item_class):
child = self.item_class(*args, **kws)
else:
child = args[0]
child.register_parent(self)
return self._add(child)
def _add(self, child_elem):
console.log("appending child", child_elem.element)
self.pre_child_append(child_elem)
child_elem.pre_append()
self._children.append(child_elem)
self.md.appendChild(child_elem.create().element)
child_elem.post_append()
self.child_appended(child_elem)
return child_elem
def pre_child_append(self, child):
pass
def child_appended(self, child):
"""Overwrite me to define logic"""
pass
class OutputCtxManager:
def __init__(self, out=None, output_to_console=True, append=True):
self._out = out
self._prev = out
self.output_to_console = output_to_console
self._append = append
def change(self, out=None, err=None, output_to_console=True, append=True):
self._prev = self._out
self._out = out
self.output_to_console = output_to_console
self._append = append
console.log("----> changed out to", self._out, self._append)
def revert(self):
console.log("----> reverted")
self._out = self._prev
def write(self, txt):
console.log("writing to", self._out, txt, self._append)
if self._out:
pyscript.write(self._out, txt, append=self._append)
if self.output_to_console:
console.log(self._out, txt)
class OutputManager:
def __init__(self, out=None, err=None, output_to_console=True, append=True):
sys.stdout = self._out_manager = OutputCtxManager(
out, output_to_console, append
)
sys.stderr = self._err_manager = OutputCtxManager(
err, output_to_console, append
)
self.output_to_console = output_to_console
self._append = append
def change(self, out=None, err=None, output_to_console=True, append=True):
self._out_manager.change(out, output_to_console, append)
sys.stdout = self._out_manager
self._err_manager.change(err, output_to_console, append)
sys.stderr = self._err_manager
self.output_to_console = output_to_console
self.append = append
def revert(self):
self._out_manager.revert()
self._err_manager.revert()
sys.stdout = self._out_manager
sys.stderr = self._err_manager
console.log("----> reverted")
pyscript = PyScript()
output_manager = OutputManager()

View File

@ -0,0 +1,51 @@
const pwa_version = "04_cell_detector_202306031006"
const assets = ["./index.html",
"./main.py",
"./resources/bootstrap.css",
"./resources/bootstrap.js",
"./resources/pyscript.css",
"./resources/pyscript.js",
"./resources/pyscript.py",
"./resources/pyscript_bootstrap_templates-0.2.0-py3-none-any.whl",
"./resources/pwa.js",
"./site.js",
]
self.addEventListener("install", installEvent => {
installEvent.waitUntil(
caches.open(pwa_version).then(cache => {
cache.addAll(assets).then(r => {
console.log("Cache assets downloaded");
}).catch(err => console.log("Error caching item", err))
console.log(`Cache ${pwa_version} opened.`);
}).catch(err => console.log("Error opening cache", err))
)
})
self.addEventListener('activate', e => {
console.log('Service Worker: Activated');
});
self.addEventListener('fetch', event => {
if (event.request.method != 'GET')
return;
event.respondWith((async () => {
const cachedResponse = await caches.match(event.request);
if (cachedResponse) {
return cachedResponse;
}
const response = await fetch(event.request);
if (!response || response.status !== 200 || response.type !== 'basic') {
return response;
}
const responseToCache = response.clone();
const cache = await caches.open(pwa_version)
await cache.put(event.request, response.clone());
return response;
})());
});

View File

@ -0,0 +1,2 @@

View File

@ -1 +1 @@
__version__ = "0.1.0" __version__ = "0.2.0"

View File

@ -323,8 +323,6 @@ class InputFile(InputElement):
name = f.name name = f.name
print("loaded ", name)
uint8_array = Uint8Array.new(await f.arrayBuffer()) uint8_array = Uint8Array.new(await f.arrayBuffer())
buffer = io.BytesIO(bytearray(uint8_array)) buffer = io.BytesIO(bytearray(uint8_array))
self._files[name] = buffer self._files[name] = buffer

View File

@ -1,10 +1,7 @@
from distutils.command.config import config
from multiprocessing.sharedctypes import Value
import pathlib import pathlib
from typing import List from typing import List
import argparse import argparse
from numpy import isin
import requests import requests
import json import json
import shutil import shutil
@ -53,6 +50,7 @@ def create_pwa_manifest(root_folder: pathlib.Path, **kwargs):
def create_pwa_service_worker(root_folder: pathlib.Path, **kwargs): def create_pwa_service_worker(root_folder: pathlib.Path, **kwargs):
version = f"{kwargs['title']}_{dt.datetime.utcnow().strftime('%Y%m%d%H%M')}" version = f"{kwargs['title']}_{dt.datetime.utcnow().strftime('%Y%m%d%H%M')}"
bslash = "\\"
assets = [ assets = [
*[f'"./{p}",' for p in kwargs['paths']], *[f'"./{p}",' for p in kwargs['paths']],
'"./index.html",', '"./index.html",',
@ -62,7 +60,7 @@ def create_pwa_service_worker(root_folder: pathlib.Path, **kwargs):
'"./resources/pyscript.css",', '"./resources/pyscript.css",',
'"./resources/pyscript.js",', '"./resources/pyscript.js",',
'"./resources/pyscript.py",', '"./resources/pyscript.py",',
f'"./resources/{kwargs["pyscript_bootstrap_templates_wheel_url"].split("/")[-1]}",', # FIXME: not completely os independent f'"./resources/{kwargs["pyscript_bootstrap_templates_wheel_url"].replace(bslash,"/").split("/")[-1]}",', # FIXME: not completely os independent
'"./resources/pwa.js",', '"./resources/pwa.js",',
'"./site.js",' '"./site.js",'
] ]
@ -148,7 +146,9 @@ def generate_project_files(root_folder: pathlib.Path,
paths = [] paths = []
pyconfig = { pyconfig = {
"autoclose_loader": True, "splashscreen":{
"autoclose": True,
},
"packages": [ "packages": [
f"./resources/{pyscript_bootstrap_templates_wheel_url.split('/')[-1]}", f"./resources/{pyscript_bootstrap_templates_wheel_url.split('/')[-1]}",
*packages *packages

View File

@ -0,0 +1,18 @@
Metadata-Version: 2.1
Name: pyscript-bootstrap-templates
Version: 0.1.0
Summary: templates and basic python/pyscript wrappers for bootstrap 5
Home-page: https://github.com/pypa/sampleproject
Author: Jonas Weinz
Author-email: author@example.com
License: UNKNOWN
Keywords: sample,setuptools,development
Platform: UNKNOWN
Requires-Python: >=3.7, <4
Description-Content-Type: text/markdown
License-File: LICENSE
# pyscript-bootstrap-templates
templates and pyscript wrappers for various bootstrap properties

View File

@ -0,0 +1,9 @@
LICENSE
README.md
setup.py
pyscript-bootstrap-templates/pyscript_bootstrap_templates.egg-info/PKG-INFO
pyscript-bootstrap-templates/pyscript_bootstrap_templates.egg-info/SOURCES.txt
pyscript-bootstrap-templates/pyscript_bootstrap_templates.egg-info/dependency_links.txt
pyscript-bootstrap-templates/pyscript_bootstrap_templates.egg-info/entry_points.txt
pyscript-bootstrap-templates/pyscript_bootstrap_templates.egg-info/requires.txt
pyscript-bootstrap-templates/pyscript_bootstrap_templates.egg-info/top_level.txt

View File

@ -0,0 +1,2 @@
[console_scripts]
create_pyscript_bootstrap_app = create:main

View File

@ -0,0 +1,2 @@
pillow
parse