new advanced example, small fixes
This commit is contained in:
		| @ -8,7 +8,7 @@ | ||||
|     "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.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_theme_color": "#ffffff" | ||||
| } | ||||
| @ -19,9 +19,11 @@ | ||||
|     <body style="width: 100%; height: 100%"> | ||||
|         <py-config type="json"> | ||||
|             { | ||||
|     "autoclose_loader": true, | ||||
|     "splashscreen": { | ||||
|         "autoclose": true | ||||
|     }, | ||||
|     "packages": [ | ||||
|         "./resources/pyscript_bootstrap_templates-0.1.0-py3-none-any.whl" | ||||
|         "./resources/pyscript_bootstrap_templates-0.2.0-py3-none-any.whl" | ||||
|     ], | ||||
|     "paths": [] | ||||
| } | ||||
|  | ||||
| @ -1,9 +1,35 @@ | ||||
| :not(:defined) { | ||||
|     display: none | ||||
| /* py-config - not a component */ | ||||
| py-config { | ||||
|     display: none; | ||||
| } | ||||
| /* py-{el} - components not defined */ | ||||
| py-script: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"; | ||||
| 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; | ||||
| } | ||||
|  | ||||
| @ -24,6 +50,7 @@ html{ | ||||
|     border-right: 4px solid rgba(255, 255, 255, 0); | ||||
|     animation: spinner 0.6s linear infinite; | ||||
| } | ||||
|  | ||||
| @keyframes spinner { | ||||
|     to { | ||||
|         transform: rotate(360deg); | ||||
| @ -81,27 +108,68 @@ html{ | ||||
|     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; | ||||
|     flex-direction: row; | ||||
|     justify-content: flex-start; | ||||
| } | ||||
|  | ||||
| .py-box div.py-box-child * | ||||
| { | ||||
| .py-box div.py-box-child * { | ||||
|     max-width: 100%; | ||||
| } | ||||
|  | ||||
| .py-repl-box{ | ||||
| .py-repl-box { | ||||
|     flex-direction: column; | ||||
| } | ||||
|  | ||||
| .editor-box{ | ||||
| .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-inset: var(--tw-empty, /*!*/ /*!*/); | ||||
|     --tw-ring-offset-width: 0px; | ||||
|     --tw-ring-offset-color: #fff; | ||||
|     --tw-ring-color: rgba(59, 130, 246, 0.5); | ||||
| @ -113,14 +181,14 @@ html{ | ||||
|     box-sizing: border-box; | ||||
|     border-width: 1px; | ||||
|     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; | ||||
| } | ||||
|  | ||||
| .repl-play-button{ | ||||
| .py-repl-run-button { | ||||
|     opacity: 0; | ||||
|     bottom: 0.25rem; | ||||
|     right: 0.25rem; | ||||
| @ -154,65 +222,64 @@ html{ | ||||
|     border: 0; | ||||
| } | ||||
|  | ||||
| .repl-play-button:hover{ | ||||
| .py-repl-run-button:hover { | ||||
|     opacity: 1; | ||||
| } | ||||
|  | ||||
| .py-title{ | ||||
| .py-title { | ||||
|     text-transform: uppercase; | ||||
|     text-align: center; | ||||
| } | ||||
|  | ||||
| .py-title h1{ | ||||
| .py-title h1 { | ||||
|     font-weight: 700; | ||||
|     font-size: 1.875rem; | ||||
| } | ||||
|  | ||||
| .py-input{ | ||||
| .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; | ||||
|     border-radius: 0.25rem; | ||||
|     margin-right: 0.75rem; | ||||
|     border-style: solid; | ||||
|     width: auto; | ||||
| } | ||||
|  | ||||
| .py-box input.py-input{ | ||||
| .py-box input.py-input { | ||||
|     width: -webkit-fill-available; | ||||
| } | ||||
|  | ||||
| .central-content{ | ||||
| .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; | ||||
|   cursor: text; | ||||
|   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; | ||||
|     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{ | ||||
| .py-button { | ||||
|     --tw-text-opacity: 1; | ||||
|     color: rgba(255, 255, 255, var(--tw-text-opacity)); | ||||
|     padding: 0.5rem; | ||||
| @ -222,17 +289,22 @@ input { | ||||
|     border-color: rgba(37, 99, 235, var(--tw-border-opacity)); | ||||
|     border-width: 1px; | ||||
|     border-radius: 0.25rem; | ||||
|     cursor: pointer; | ||||
| } | ||||
|  | ||||
| .py-li-element p{ | ||||
| .py-li-element p { | ||||
|     margin: 5px; | ||||
| } | ||||
|  | ||||
| .py-li-element p{ | ||||
| .py-li-element p { | ||||
|     display: inline; | ||||
| } | ||||
|  | ||||
| button, input, optgroup, select, textarea { | ||||
| button, | ||||
| input, | ||||
| optgroup, | ||||
| select, | ||||
| textarea { | ||||
|     font-family: inherit; | ||||
|     font-size: 100%; | ||||
|     line-height: 1.15; | ||||
| @ -242,3 +314,19 @@ button, input, optgroup, select, textarea { | ||||
| .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
											
										
									
								
							
										
											Binary file not shown.
										
									
								
							
										
											Binary file not shown.
										
									
								
							| @ -1,5 +1,5 @@ | ||||
|  | ||||
| const pwa_version = "01_hello_world_202210161638" | ||||
| const pwa_version = "01_hello_world_202306031006" | ||||
| const assets = ["./index.html", | ||||
|     "./main.py", | ||||
|     "./resources/bootstrap.css", | ||||
| @ -7,7 +7,7 @@ const assets = ["./index.html", | ||||
|     "./resources/pyscript.css", | ||||
|     "./resources/pyscript.js", | ||||
|     "./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", | ||||
|     "./site.js", | ||||
| ] | ||||
|  | ||||
| @ -11,7 +11,7 @@ | ||||
|     "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.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_theme_color": "#ffffff" | ||||
| } | ||||
| @ -19,9 +19,11 @@ | ||||
|     <body style="width: 100%; height: 100%"> | ||||
|         <py-config type="json"> | ||||
|             { | ||||
|     "autoclose_loader": true, | ||||
|     "splashscreen": { | ||||
|         "autoclose": true | ||||
|     }, | ||||
|     "packages": [ | ||||
|         "./resources/pyscript_bootstrap_templates-0.1.0-py3-none-any.whl", | ||||
|         "./resources/pyscript_bootstrap_templates-0.2.0-py3-none-any.whl", | ||||
|         "numpy", | ||||
|         "scikit-image" | ||||
|     ], | ||||
|  | ||||
| @ -1,9 +1,35 @@ | ||||
| :not(:defined) { | ||||
|     display: none | ||||
| /* py-config - not a component */ | ||||
| py-config { | ||||
|     display: none; | ||||
| } | ||||
| /* py-{el} - components not defined */ | ||||
| py-script: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"; | ||||
| 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; | ||||
| } | ||||
|  | ||||
| @ -24,6 +50,7 @@ html{ | ||||
|     border-right: 4px solid rgba(255, 255, 255, 0); | ||||
|     animation: spinner 0.6s linear infinite; | ||||
| } | ||||
|  | ||||
| @keyframes spinner { | ||||
|     to { | ||||
|         transform: rotate(360deg); | ||||
| @ -81,27 +108,68 @@ html{ | ||||
|     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; | ||||
|     flex-direction: row; | ||||
|     justify-content: flex-start; | ||||
| } | ||||
|  | ||||
| .py-box div.py-box-child * | ||||
| { | ||||
| .py-box div.py-box-child * { | ||||
|     max-width: 100%; | ||||
| } | ||||
|  | ||||
| .py-repl-box{ | ||||
| .py-repl-box { | ||||
|     flex-direction: column; | ||||
| } | ||||
|  | ||||
| .editor-box{ | ||||
| .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-inset: var(--tw-empty, /*!*/ /*!*/); | ||||
|     --tw-ring-offset-width: 0px; | ||||
|     --tw-ring-offset-color: #fff; | ||||
|     --tw-ring-color: rgba(59, 130, 246, 0.5); | ||||
| @ -113,14 +181,14 @@ html{ | ||||
|     box-sizing: border-box; | ||||
|     border-width: 1px; | ||||
|     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; | ||||
| } | ||||
|  | ||||
| .repl-play-button{ | ||||
| .py-repl-run-button { | ||||
|     opacity: 0; | ||||
|     bottom: 0.25rem; | ||||
|     right: 0.25rem; | ||||
| @ -154,65 +222,64 @@ html{ | ||||
|     border: 0; | ||||
| } | ||||
|  | ||||
| .repl-play-button:hover{ | ||||
| .py-repl-run-button:hover { | ||||
|     opacity: 1; | ||||
| } | ||||
|  | ||||
| .py-title{ | ||||
| .py-title { | ||||
|     text-transform: uppercase; | ||||
|     text-align: center; | ||||
| } | ||||
|  | ||||
| .py-title h1{ | ||||
| .py-title h1 { | ||||
|     font-weight: 700; | ||||
|     font-size: 1.875rem; | ||||
| } | ||||
|  | ||||
| .py-input{ | ||||
| .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; | ||||
|     border-radius: 0.25rem; | ||||
|     margin-right: 0.75rem; | ||||
|     border-style: solid; | ||||
|     width: auto; | ||||
| } | ||||
|  | ||||
| .py-box input.py-input{ | ||||
| .py-box input.py-input { | ||||
|     width: -webkit-fill-available; | ||||
| } | ||||
|  | ||||
| .central-content{ | ||||
| .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; | ||||
|   cursor: text; | ||||
|   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; | ||||
|     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{ | ||||
| .py-button { | ||||
|     --tw-text-opacity: 1; | ||||
|     color: rgba(255, 255, 255, var(--tw-text-opacity)); | ||||
|     padding: 0.5rem; | ||||
| @ -222,17 +289,22 @@ input { | ||||
|     border-color: rgba(37, 99, 235, var(--tw-border-opacity)); | ||||
|     border-width: 1px; | ||||
|     border-radius: 0.25rem; | ||||
|     cursor: pointer; | ||||
| } | ||||
|  | ||||
| .py-li-element p{ | ||||
| .py-li-element p { | ||||
|     margin: 5px; | ||||
| } | ||||
|  | ||||
| .py-li-element p{ | ||||
| .py-li-element p { | ||||
|     display: inline; | ||||
| } | ||||
|  | ||||
| button, input, optgroup, select, textarea { | ||||
| button, | ||||
| input, | ||||
| optgroup, | ||||
| select, | ||||
| textarea { | ||||
|     font-family: inherit; | ||||
|     font-size: 100%; | ||||
|     line-height: 1.15; | ||||
| @ -242,3 +314,19 @@ button, input, optgroup, select, textarea { | ||||
| .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
											
										
									
								
							
										
											Binary file not shown.
										
									
								
							
										
											Binary file not shown.
										
									
								
							| @ -1,5 +1,5 @@ | ||||
|  | ||||
| const pwa_version = "02_image_filter_202210161638" | ||||
| const pwa_version = "02_image_filter_202306031006" | ||||
| const assets = ["./index.html", | ||||
|     "./main.py", | ||||
|     "./resources/bootstrap.css", | ||||
| @ -7,7 +7,7 @@ const assets = ["./index.html", | ||||
|     "./resources/pyscript.css", | ||||
|     "./resources/pyscript.js", | ||||
|     "./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", | ||||
|     "./site.js", | ||||
| ] | ||||
|  | ||||
| @ -10,7 +10,7 @@ | ||||
|     "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.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_theme_color": "#ffffff" | ||||
| } | ||||
| @ -19,9 +19,11 @@ | ||||
|     <body style="width: 100%; height: 100%"> | ||||
|         <py-config type="json"> | ||||
|             { | ||||
|     "autoclose_loader": true, | ||||
|     "splashscreen": { | ||||
|         "autoclose": true | ||||
|     }, | ||||
|     "packages": [ | ||||
|         "./resources/pyscript_bootstrap_templates-0.1.0-py3-none-any.whl", | ||||
|         "./resources/pyscript_bootstrap_templates-0.2.0-py3-none-any.whl", | ||||
|         "numpy" | ||||
|     ], | ||||
|     "paths": [] | ||||
|  | ||||
| @ -1,9 +1,35 @@ | ||||
| :not(:defined) { | ||||
|     display: none | ||||
| /* py-config - not a component */ | ||||
| py-config { | ||||
|     display: none; | ||||
| } | ||||
| /* py-{el} - components not defined */ | ||||
| py-script: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"; | ||||
| 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; | ||||
| } | ||||
|  | ||||
| @ -24,6 +50,7 @@ html{ | ||||
|     border-right: 4px solid rgba(255, 255, 255, 0); | ||||
|     animation: spinner 0.6s linear infinite; | ||||
| } | ||||
|  | ||||
| @keyframes spinner { | ||||
|     to { | ||||
|         transform: rotate(360deg); | ||||
| @ -81,27 +108,68 @@ html{ | ||||
|     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; | ||||
|     flex-direction: row; | ||||
|     justify-content: flex-start; | ||||
| } | ||||
|  | ||||
| .py-box div.py-box-child * | ||||
| { | ||||
| .py-box div.py-box-child * { | ||||
|     max-width: 100%; | ||||
| } | ||||
|  | ||||
| .py-repl-box{ | ||||
| .py-repl-box { | ||||
|     flex-direction: column; | ||||
| } | ||||
|  | ||||
| .editor-box{ | ||||
| .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-inset: var(--tw-empty, /*!*/ /*!*/); | ||||
|     --tw-ring-offset-width: 0px; | ||||
|     --tw-ring-offset-color: #fff; | ||||
|     --tw-ring-color: rgba(59, 130, 246, 0.5); | ||||
| @ -113,14 +181,14 @@ html{ | ||||
|     box-sizing: border-box; | ||||
|     border-width: 1px; | ||||
|     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; | ||||
| } | ||||
|  | ||||
| .repl-play-button{ | ||||
| .py-repl-run-button { | ||||
|     opacity: 0; | ||||
|     bottom: 0.25rem; | ||||
|     right: 0.25rem; | ||||
| @ -154,65 +222,64 @@ html{ | ||||
|     border: 0; | ||||
| } | ||||
|  | ||||
| .repl-play-button:hover{ | ||||
| .py-repl-run-button:hover { | ||||
|     opacity: 1; | ||||
| } | ||||
|  | ||||
| .py-title{ | ||||
| .py-title { | ||||
|     text-transform: uppercase; | ||||
|     text-align: center; | ||||
| } | ||||
|  | ||||
| .py-title h1{ | ||||
| .py-title h1 { | ||||
|     font-weight: 700; | ||||
|     font-size: 1.875rem; | ||||
| } | ||||
|  | ||||
| .py-input{ | ||||
| .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; | ||||
|     border-radius: 0.25rem; | ||||
|     margin-right: 0.75rem; | ||||
|     border-style: solid; | ||||
|     width: auto; | ||||
| } | ||||
|  | ||||
| .py-box input.py-input{ | ||||
| .py-box input.py-input { | ||||
|     width: -webkit-fill-available; | ||||
| } | ||||
|  | ||||
| .central-content{ | ||||
| .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; | ||||
|   cursor: text; | ||||
|   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; | ||||
|     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{ | ||||
| .py-button { | ||||
|     --tw-text-opacity: 1; | ||||
|     color: rgba(255, 255, 255, var(--tw-text-opacity)); | ||||
|     padding: 0.5rem; | ||||
| @ -222,17 +289,22 @@ input { | ||||
|     border-color: rgba(37, 99, 235, var(--tw-border-opacity)); | ||||
|     border-width: 1px; | ||||
|     border-radius: 0.25rem; | ||||
|     cursor: pointer; | ||||
| } | ||||
|  | ||||
| .py-li-element p{ | ||||
| .py-li-element p { | ||||
|     margin: 5px; | ||||
| } | ||||
|  | ||||
| .py-li-element p{ | ||||
| .py-li-element p { | ||||
|     display: inline; | ||||
| } | ||||
|  | ||||
| button, input, optgroup, select, textarea { | ||||
| button, | ||||
| input, | ||||
| optgroup, | ||||
| select, | ||||
| textarea { | ||||
|     font-family: inherit; | ||||
|     font-size: 100%; | ||||
|     line-height: 1.15; | ||||
| @ -242,3 +314,19 @@ button, input, optgroup, select, textarea { | ||||
| .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
											
										
									
								
							
										
											Binary file not shown.
										
									
								
							
										
											Binary file not shown.
										
									
								
							| @ -1,5 +1,5 @@ | ||||
|  | ||||
| const pwa_version = "03_numpy_grid_demo_202210161638" | ||||
| const pwa_version = "03_numpy_grid_demo_202306031006" | ||||
| const assets = ["./index.html", | ||||
|     "./main.py", | ||||
|     "./resources/bootstrap.css", | ||||
| @ -7,7 +7,7 @@ const assets = ["./index.html", | ||||
|     "./resources/pyscript.css", | ||||
|     "./resources/pyscript.js", | ||||
|     "./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", | ||||
|     "./site.js", | ||||
| ] | ||||
|  | ||||
							
								
								
									
										18
									
								
								examples/04_cell_detector/config.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								examples/04_cell_detector/config.json
									
									
									
									
									
										Normal 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" | ||||
| } | ||||
							
								
								
									
										40
									
								
								examples/04_cell_detector/index.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										40
									
								
								examples/04_cell_detector/index.html
									
									
									
									
									
										Normal 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> | ||||
|      | ||||
							
								
								
									
										214
									
								
								examples/04_cell_detector/main.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										214
									
								
								examples/04_cell_detector/main.py
									
									
									
									
									
										Normal 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 | ||||
|  | ||||
|      | ||||
							
								
								
									
										1
									
								
								examples/04_cell_detector/manifest.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								examples/04_cell_detector/manifest.json
									
									
									
									
									
										Normal 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"}]} | ||||
							
								
								
									
										7
									
								
								examples/04_cell_detector/resources/bootstrap.css
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								examples/04_cell_detector/resources/bootstrap.css
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
							
								
								
									
										7
									
								
								examples/04_cell_detector/resources/bootstrap.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								examples/04_cell_detector/resources/bootstrap.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								examples/04_cell_detector/resources/icon-512x512.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								examples/04_cell_detector/resources/icon-512x512.png
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 18 KiB | 
							
								
								
									
										10
									
								
								examples/04_cell_detector/resources/pwa.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								examples/04_cell_detector/resources/pwa.js
									
									
									
									
									
										Normal 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)) | ||||
|     }) | ||||
| } | ||||
|      | ||||
							
								
								
									
										332
									
								
								examples/04_cell_detector/resources/pyscript.css
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										332
									
								
								examples/04_cell_detector/resources/pyscript.css
									
									
									
									
									
										Normal 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; | ||||
| } | ||||
							
								
								
									
										28907
									
								
								examples/04_cell_detector/resources/pyscript.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										28907
									
								
								examples/04_cell_detector/resources/pyscript.js
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
							
								
								
									
										424
									
								
								examples/04_cell_detector/resources/pyscript.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										424
									
								
								examples/04_cell_detector/resources/pyscript.py
									
									
									
									
									
										Normal 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() | ||||
										
											Binary file not shown.
										
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										51
									
								
								examples/04_cell_detector/serviceWorker.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										51
									
								
								examples/04_cell_detector/serviceWorker.js
									
									
									
									
									
										Normal 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; | ||||
|     })()); | ||||
| }); | ||||
|      | ||||
							
								
								
									
										2
									
								
								examples/04_cell_detector/site.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								examples/04_cell_detector/site.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,2 @@ | ||||
|  | ||||
|      | ||||
		Reference in New Issue
	
	Block a user