diff --git a/build.sh b/build.sh index f761f8c..1231cd6 100755 --- a/build.sh +++ b/build.sh @@ -1,6 +1,6 @@ #!/bin/bash -rm -rf ./dist/ +#rm -rf ./dist/ python setup.py bdist_wheel wheel_files=( dist/*whl ) diff --git a/dist/pyscript_bootstrap_templates-0.2.0-py3-none-any.whl b/dist/pyscript_bootstrap_templates-0.2.0-py3-none-any.whl new file mode 100644 index 0000000..d8ece2b Binary files /dev/null and b/dist/pyscript_bootstrap_templates-0.2.0-py3-none-any.whl differ diff --git a/examples/01_hello_world/main.py b/examples/01_hello_world/main.py index f7829a8..b41f49e 100644 --- a/examples/01_hello_world/main.py +++ b/examples/01_hello_world/main.py @@ -20,6 +20,8 @@ toast_container.position_end = 0 def onclick(_): + # NOTE: you can also use app.toast(...) and app.alert_success(...) to show toasts + # and alerts at the default bottom right position alert = bHTML.AlertSuccess("You clicked me!", parent=app.main) alert.w = 25 toast = bHTML.Toast("You clicked me!", parent=toast_container) @@ -27,4 +29,3 @@ def onclick(_): toast.show() btn.onclick = onclick - \ No newline at end of file diff --git a/examples/01_hello_world/resources/pyscript_bootstrap_templates-0.2.0-py3-none-any.whl b/examples/01_hello_world/resources/pyscript_bootstrap_templates-0.2.0-py3-none-any.whl index e1a2da0..d8ece2b 100644 Binary files a/examples/01_hello_world/resources/pyscript_bootstrap_templates-0.2.0-py3-none-any.whl and b/examples/01_hello_world/resources/pyscript_bootstrap_templates-0.2.0-py3-none-any.whl differ diff --git a/examples/01_hello_world/serviceWorker.js b/examples/01_hello_world/serviceWorker.js index 106a7b1..2532e0f 100644 --- a/examples/01_hello_world/serviceWorker.js +++ b/examples/01_hello_world/serviceWorker.js @@ -1,5 +1,5 @@ -const pwa_version = "01_hello_world_202306031006" +const pwa_version = "01_hello_world_202306041650" const assets = ["./index.html", "./main.py", "./resources/bootstrap.css", @@ -48,4 +48,15 @@ self.addEventListener('fetch', event => { return response; })()); }); +self.addEventListener('activate', (event) => { + event.waitUntil( + caches.keys().then((keyList) => { + return Promise.all(keyList.map((key) => { + if(key !== pwa_version) { + return caches.delete(key); + } + })); + }) + ); +}); \ No newline at end of file diff --git a/examples/02_image_filter/resources/pyscript_bootstrap_templates-0.2.0-py3-none-any.whl b/examples/02_image_filter/resources/pyscript_bootstrap_templates-0.2.0-py3-none-any.whl index e1a2da0..d8ece2b 100644 Binary files a/examples/02_image_filter/resources/pyscript_bootstrap_templates-0.2.0-py3-none-any.whl and b/examples/02_image_filter/resources/pyscript_bootstrap_templates-0.2.0-py3-none-any.whl differ diff --git a/examples/02_image_filter/serviceWorker.js b/examples/02_image_filter/serviceWorker.js index 8dedfd2..9af34de 100644 --- a/examples/02_image_filter/serviceWorker.js +++ b/examples/02_image_filter/serviceWorker.js @@ -1,5 +1,5 @@ -const pwa_version = "02_image_filter_202306031006" +const pwa_version = "02_image_filter_202306041650" const assets = ["./index.html", "./main.py", "./resources/bootstrap.css", @@ -48,4 +48,15 @@ self.addEventListener('fetch', event => { return response; })()); }); +self.addEventListener('activate', (event) => { + event.waitUntil( + caches.keys().then((keyList) => { + return Promise.all(keyList.map((key) => { + if(key !== pwa_version) { + return caches.delete(key); + } + })); + }) + ); +}); \ No newline at end of file diff --git a/examples/03_numpy_grid_demo/resources/pyscript_bootstrap_templates-0.2.0-py3-none-any.whl b/examples/03_numpy_grid_demo/resources/pyscript_bootstrap_templates-0.2.0-py3-none-any.whl index e1a2da0..d8ece2b 100644 Binary files a/examples/03_numpy_grid_demo/resources/pyscript_bootstrap_templates-0.2.0-py3-none-any.whl and b/examples/03_numpy_grid_demo/resources/pyscript_bootstrap_templates-0.2.0-py3-none-any.whl differ diff --git a/examples/03_numpy_grid_demo/serviceWorker.js b/examples/03_numpy_grid_demo/serviceWorker.js index b2960e0..2b8049d 100644 --- a/examples/03_numpy_grid_demo/serviceWorker.js +++ b/examples/03_numpy_grid_demo/serviceWorker.js @@ -1,5 +1,5 @@ -const pwa_version = "03_numpy_grid_demo_202306031006" +const pwa_version = "03_numpy_grid_demo_202306041650" const assets = ["./index.html", "./main.py", "./resources/bootstrap.css", @@ -48,4 +48,15 @@ self.addEventListener('fetch', event => { return response; })()); }); +self.addEventListener('activate', (event) => { + event.waitUntil( + caches.keys().then((keyList) => { + return Promise.all(keyList.map((key) => { + if(key !== pwa_version) { + return caches.delete(key); + } + })); + }) + ); +}); \ No newline at end of file diff --git a/examples/04_cell_detector/main.py b/examples/04_cell_detector/main.py index 774ac2f..0296ddb 100644 --- a/examples/04_cell_detector/main.py +++ b/examples/04_cell_detector/main.py @@ -9,13 +9,13 @@ import numpy as np from PIL import Image import cv2 -loaded_img = None +loaded_img:np.ndarray = None -app = bootstrap_templates.PyScriptBootstrapDashboard(parent_element="pyscript_app", brand_name="Pyscript Cell Detector") +app = bootstrap_templates.PyScriptBootstrapDashboard(parent_element="pyscript_app", brand_name="Pyscript Cell Colony Detector") main_div = bHTML.BootstrapContainer(parent=app.main) main_div.w = 100 -result_div = bHTML.BootstrapContainer(parent=main_div) +result_div = bHTML.BootstrapContainer (parent=main_div) def process_image(image, @@ -24,13 +24,20 @@ def process_image(image, hough_param2 = 500, minRadius = 100, maxRadius=500, - inner_hough_circles=True): + inner_hough_param1 = 25, + inner_hough_param2 = 50, + inner_hough_circles=True, + cell_colony_color_channel:int = None): 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) + if cell_colony_color_channel is None: + gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) + else: + gray = image.astype(float)[...,cell_colony_color_channel] + gray = (255 * gray / gray.max()).astype(np.uint8) # Use median blur to reduce noise gray = cv2.medianBlur(gray, 5) @@ -46,7 +53,7 @@ def process_image(image, maxRadius=maxRadius) if circles is None: - bHTML.AlertDanger("No cell areas detected in image", parent=app.main) + app.alert_danger("No cell areas detected in image") return False # Convert to integers @@ -84,7 +91,14 @@ def process_image(image, 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) + circles = cv2.HoughCircles(well_gray, + cv2.HOUGH_GRADIENT, + 1, + minDist=hough_min_dist, + param1=inner_hough_param1, + param2=inner_hough_param2, + minRadius=int((min(well.shape[0],well.shape[1]) // 2)*0.79), + maxRadius=int((min(well.shape[0],well.shape[1]) // 2)*0.95)) if circles is not None: circles = np.uint16(np.around(circles)) i = circles[0,:][0] @@ -109,9 +123,11 @@ def process_image(image, second_wells = first_wells second_wells_gray = first_wells_gray + tabs = {} - for well, well_gray in zip(second_wells, second_wells_gray): + for well in second_wells: + well_gray = cv2.cvtColor(well, cv2.COLOR_BGR2GRAY) _, binary = cv2.threshold(255 - well_gray, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU) @@ -120,29 +136,37 @@ def process_image(image, cleaned = cv2.morphologyEx(binary, cv2.MORPH_OPEN, kernel) - radius = well.shape[0] // 2 -1 + radius = min(well.shape[0], well.shape[1]) // 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) + cv2.circle(well_mask, (well.shape[1] // 2, well.shape[0] // 2), 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 = bHTML.BootstrapContainer(f"ratio: {ratio * 100}%") div_result.shadow = bHTML.Shadow.LARGE div_result.rounded = True - div_result.w = 50 + div_result.w = 75 + div_result.h = 75 + div_result.rounded_size = 50 + div_result.p = 3 + well = well.copy() 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.rounded_size = 50 final_img.shadow = bHTML.Shadow.LARGE final_img.w = 100 + tabs[f"Well #{len(tabs) + 1}"] = div_result + + tabs = bHTML.Tabs(tabs, parent=result_div) + tabs.w = 100 div = bHTML.BootstrapContainer("Controls", parent=app.sidebar) @@ -150,64 +174,135 @@ 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 +btn.mt = 3 +btn.mb = 3 +btn.ml = 4 +btn.mr = 4 + +i_hough_min_dist = bInputs.InputInt("hough min distance [px]", + parent=app.sidebar, + help_text="minimal distance between wells in pixels") +i_hough_min_dist.value = 500 + +i_hough_param1 = bInputs.InputInt("hough param1", + parent=app.sidebar, + help_text="parameter for canny edge detector") +i_hough_param1.value = 80 + +i_hough_param2 = bInputs.InputInt("hough param2", + parent=app.sidebar, + help_text="increase this value to prevent false circle detection") +i_hough_param2.value = 500 + +i_hough_min_radius = bInputs.InputInt("hough min radius [px]", + parent=app.sidebar, + help_text="min radius for circle detection") +i_hough_min_radius.value = 100 + +i_hough_max_radius = bInputs.InputInt("hough max radius [px]", + parent=app.sidebar, + help_text="max radius for circle detection") +i_hough_max_radius.value = 500 + +i_inner_hough = bInputs.InputCheckboxSingle("nested hough transform?", + parent=app.sidebar, + help_text="if set, circle detection will be applied twice on detected wells") +i_inner_hough.value = True +i_inner_hough.m = 3 + +i_inner_hough_param1 = bInputs.InputInt("inner hough param1", parent=app.sidebar) +i_inner_hough_param1.value = 25 + +i_inner_hough_param2 = bInputs.InputInt("inner hough param2", parent=app.sidebar) +i_inner_hough_param2.value = 50 + +i_use_color_channel = bInputs.InputSelect(["all", + "red", + "green", + "blue"], + label_text="cell colony color channel", + parent=app.sidebar, + help_text="if cells are more present in a specific channel, select it here") +i_use_color_channel.value = "blue" + + 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 + + try: + global loaded_img + f.seek(0) + img = np.array(Image.open(f)) + for child in result_div.children: + child.destroy() + + bHTML.BootstrapContainer("input image:", parent=result_div) + output = bHTML.Image.from_numpy_array(img, parent=result_div) + output.rounded = True + output.rounded_size = 10 + output.shadow = bHTML.Shadow.LARGE + output.width = "100%" + output.height = "100%" + + loaded_img = img + + app.toast("successfully loaded image", "info") + #toast = bHTML.Toast("successfully loaded image", title="info", parent=toast_container) + #toast.animation = True + #toast.show() + except Exception as e: + for child in result_div.children: + child.destroy() + bHTML.AlertDanger(f"error while loading image: {str(e)}", parent=alert_container) + image_input.onchange = on_image_change def on_click(*args, **kwargs): if loaded_img is None: - bHTML.AlertDanger("No image loaded", parent=app.main) + app.alert_danger("No image loaded") 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) + for child in result_div.children: + child.destroy() + + try: + h_min_dist = int(i_hough_min_dist.value) + h_param1 = int(i_hough_param1.value) + h_param2 = int(i_hough_param2.value) + h_inner_param1 = int(i_inner_hough_param1.value) + h_inner_param2 = int(i_inner_hough_param2.value) + h_min_radius = int(i_hough_min_radius.value) + h_max_radius = int(i_hough_max_radius.value) + inner_hough = bool(i_inner_hough.value) + color_channel = { + "all": None, + "red": 0, + "green": 1, + "blue": 2 + }[i_use_color_channel.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) + + 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_param1=h_inner_param1, + inner_hough_param2=h_inner_param2, + inner_hough_circles=inner_hough, + cell_colony_color_channel=color_channel) + app.toast("successfully processed image", title="info") + + except Exception as e: + for child in result_div.children: + child.destroy() + app.alert_danger(f"error while processing image: {str(e)}") btn.onclick = on_click diff --git a/examples/04_cell_detector/resources/pyscript_bootstrap_templates-0.2.0-py3-none-any.whl b/examples/04_cell_detector/resources/pyscript_bootstrap_templates-0.2.0-py3-none-any.whl index e1a2da0..d8ece2b 100644 Binary files a/examples/04_cell_detector/resources/pyscript_bootstrap_templates-0.2.0-py3-none-any.whl and b/examples/04_cell_detector/resources/pyscript_bootstrap_templates-0.2.0-py3-none-any.whl differ diff --git a/examples/04_cell_detector/serviceWorker.js b/examples/04_cell_detector/serviceWorker.js index cbe4e10..d33c682 100644 --- a/examples/04_cell_detector/serviceWorker.js +++ b/examples/04_cell_detector/serviceWorker.js @@ -1,5 +1,5 @@ -const pwa_version = "04_cell_detector_202306031006" +const pwa_version = "04_cell_detector_202306041650" const assets = ["./index.html", "./main.py", "./resources/bootstrap.css", @@ -48,4 +48,15 @@ self.addEventListener('fetch', event => { return response; })()); }); +self.addEventListener('activate', (event) => { + event.waitUntil( + caches.keys().then((keyList) => { + return Promise.all(keyList.map((key) => { + if(key !== pwa_version) { + return caches.delete(key); + } + })); + }) + ); +}); \ No newline at end of file diff --git a/pyscript_bootstrap_templates/HTML.py b/pyscript_bootstrap_templates/HTML.py index 13147e7..1a947d0 100644 --- a/pyscript_bootstrap_templates/HTML.py +++ b/pyscript_bootstrap_templates/HTML.py @@ -68,6 +68,7 @@ class Element(object): self._display = self.element.style.display self.element.style.display = "none" + def show(self) -> None: if self._display is None: return @@ -209,6 +210,27 @@ class Element(object): def write(self, object): self.element.write(self.id, object) # type: ignore + + def set_style(self, property_name: str, value: str) -> None: + """Set a CSS style property on this element.""" + setattr(self._element.style, property_name, value) + + def get_style(self, property_name: str) -> str: + """Get the value of a CSS style property on this element.""" + try: + return getattr(self._element.style, property_name) + except AttributeError: + return None + + def remove_style(self, property_name: str) -> None: + """Remove a CSS style property from this element.""" + setattr(self._element.style, property_name, None) + + def set_styles(self, **styles) -> None: + """Set multiple CSS style properties on this element.""" + for property_name, value in styles.items(): + self.set_style(property_name, value) + class A(Element): diff --git a/pyscript_bootstrap_templates/__pycache__/__init__.cpython-310.pyc b/pyscript_bootstrap_templates/__pycache__/__init__.cpython-310.pyc deleted file mode 100644 index a66062b..0000000 Binary files a/pyscript_bootstrap_templates/__pycache__/__init__.cpython-310.pyc and /dev/null differ diff --git a/pyscript_bootstrap_templates/__pycache__/cli_interface.cpython-310.pyc b/pyscript_bootstrap_templates/__pycache__/cli_interface.cpython-310.pyc deleted file mode 100644 index be569c1..0000000 Binary files a/pyscript_bootstrap_templates/__pycache__/cli_interface.cpython-310.pyc and /dev/null differ diff --git a/pyscript_bootstrap_templates/__pycache__/create.cpython-310.pyc b/pyscript_bootstrap_templates/__pycache__/create.cpython-310.pyc deleted file mode 100644 index 2136191..0000000 Binary files a/pyscript_bootstrap_templates/__pycache__/create.cpython-310.pyc and /dev/null differ diff --git a/pyscript_bootstrap_templates/bootstrap_HTML.py b/pyscript_bootstrap_templates/bootstrap_HTML.py index 5ccb562..8f5de7a 100644 --- a/pyscript_bootstrap_templates/bootstrap_HTML.py +++ b/pyscript_bootstrap_templates/bootstrap_HTML.py @@ -796,16 +796,22 @@ class Col(BootstrapContainer): class_name: str = None, parent: HTML.Element = None, col: int = None, + col_xs: int = None, col_sm: int = None, col_md: int = None, col_lg: int = None, col_xl: int = None, col_xxl: int = None) -> None: + + super().__init__(inner_html, id, class_name, parent) + + if col is not None: self.col = col - super().__init__(inner_html, id, class_name, parent) + if col_xs is not None: + self.col_xs = col_xs if col_sm is not None: self.col_sm = col_sm @@ -1110,6 +1116,9 @@ class NavbarBrand(HTML.A, BootstrapContainer): parent=parent) self.set_attribute("href", href) + self.ms = 3 + self.me = 3 + class Navbar(BootstrapContainer): diff --git a/pyscript_bootstrap_templates/bootstrap_HTML_container.py b/pyscript_bootstrap_templates/bootstrap_HTML_container.py index cdbd534..db3cf20 100644 --- a/pyscript_bootstrap_templates/bootstrap_HTML_container.py +++ b/pyscript_bootstrap_templates/bootstrap_HTML_container.py @@ -314,6 +314,15 @@ class BootstrapContainer(HTML.Div): def text_align(self, value: Union[TextAlign, None]): self._set_enum_property( value=value, prefix="text-", enum_class=TextAlign, enum_values=_TEXT_ALIGNS) + + @property + def text_align_xs(self) -> Union[TextAlign, None]: + return self._get_enum_property(prefix="text-", enum_class=TextAlign, enum_values=_TEXT_ALIGNS, breakpoint="xs") + + @text_align_xs.setter + def text_align_xs(self, value: Union[TextAlign, None]): + self._set_enum_property(value=value, prefix="text-", + enum_class=TextAlign, enum_values=_TEXT_ALIGNS, breakpoint="xs") @property def text_align_sm(self) -> Union[TextAlign, None]: @@ -558,6 +567,15 @@ class BootstrapContainer(HTML.Div): def display_property(self, value: Union[DisplayProperty, None]): self._set_enum_property( value=value, prefix="d-", enum_class=DisplayProperty, enum_values=_DISPLAY_PROPERTIES) + + @property + def display_property_xs(self) -> Union[DisplayProperty, None]: + return self._get_enum_property(prefix="d-", enum_class=DisplayProperty, enum_values=_DISPLAY_PROPERTIES, breakpoint="xs") + + @display_property_xs.setter + def display_property_xs(self, value: Union[DisplayProperty, None]): + self._set_enum_property(value=value, prefix="d-", enum_class=DisplayProperty, + enum_values=_DISPLAY_PROPERTIES, breakpoint="xs") @property def display_property_sm(self) -> Union[DisplayProperty, None]: @@ -1112,6 +1130,17 @@ class BootstrapContainer(HTML.Div): @p.setter def p(self, value: Union[int, str, None]): self._set_css_param_number("p-", value) + + @property + def p_xs(self) -> Union[int, str, None]: + """ + element's padding for small screens. By default, bootstrap defines the values 0,1,2,3,4,5,auto + """ + return self._get_css_param_number("p-", "xs") + + @p_xs.setter + def p_xs(self, value: Union[int, str, None]): + self._set_css_param_number("p-", value, "xs") @property def p_sm(self) -> Union[int, str, None]: @@ -1178,6 +1207,17 @@ class BootstrapContainer(HTML.Div): @px.setter def px(self, value: Union[int, str, None]): self._set_css_param_number("px-", value) + + @property + def px_xs(self) -> Union[int, str, None]: + """ + element's horizontal padding for small screens. By default, bootstrap defines the values 0,1,2,3,4,5,auto + """ + return self._get_css_param_number("px-", "xs") + + @px_xs.setter + def px_xs(self, value: Union[int, str, None]): + self._set_css_param_number("px-", value, "xs") @property def px_sm(self) -> Union[int, str, None]: @@ -1245,6 +1285,17 @@ class BootstrapContainer(HTML.Div): def py(self, value: Union[int, str, None]): self._set_css_param_number("py-", value) + @property + def py_xs(self) -> Union[int, str, None]: + """ + element's vertical padding for small screens. By default, bootstrap defines the values 0,1,2,3,4,5,auto + """ + return self._get_css_param_number("py-", "xs") + + @py_xs.setter + def py_xs(self, value: Union[int, str, None]): + self._set_css_param_number("py-", value, "xs") + @property def py_sm(self) -> Union[int, str, None]: """ @@ -1311,6 +1362,17 @@ class BootstrapContainer(HTML.Div): def pt(self, value: Union[int, str, None]): self._set_css_param_number("pt-", value) + @property + def pt_xs(self) -> Union[int, str, None]: + """ + element's top padding for small screens. By default, bootstrap defines the values 0,1,2,3,4,5,auto + """ + return self._get_css_param_number("pt-", "xs") + + @pt_xs.setter + def pt_xs(self, value: Union[int, str, None]): + self._set_css_param_number("pt-", value, "xs") + @property def pt_sm(self) -> Union[int, str, None]: """ @@ -1376,6 +1438,17 @@ class BootstrapContainer(HTML.Div): @pb.setter def pb(self, value: Union[int, str, None]): self._set_css_param_number("pb-", value) + + @property + def pb_xs(self) -> Union[int, str, None]: + """ + element's bottom padding for small screens. By default, bootstrap defines the values 0,1,2,3,4,5,auto + """ + return self._get_css_param_number("pb-", "xs") + + @pb_xs.setter + def pb_xs(self, value: Union[int, str, None]): + self._set_css_param_number("pb-", value, "xs") @property def pb_sm(self) -> Union[int, str, None]: @@ -1442,6 +1515,17 @@ class BootstrapContainer(HTML.Div): @ps.setter def ps(self, value: Union[int, str, None]): self._set_css_param_number("ps-", value) + + @property + def ps_xs(self) -> Union[int, str, None]: + """ + element's start padding (left for LTR languages) for small screens. By default, bootstrap defines the values 0,1,2,3,4,5,auto + """ + return self._get_css_param_number("ps-", "xs") + + @ps_xs.setter + def ps_xs(self, value: Union[int, str, None]): + self._set_css_param_number("ps-", value, "xs") @property def ps_sm(self) -> Union[int, str, None]: @@ -1508,6 +1592,17 @@ class BootstrapContainer(HTML.Div): @pe.setter def pe(self, value: Union[int, str, None]): self._set_css_param_number("pe-", value) + + @property + def pe_xs(self) -> Union[int, str, None]: + """ + element's end padding (right for LTR languages) for small screens. By default, bootstrap defines the values 0,1,2,3,4,5,auto + """ + return self._get_css_param_number("pe-", "xs") + + @pe_xs.setter + def pe_xs(self, value: Union[int, str, None]): + self._set_css_param_number("pe-", value, "xs") @property def pe_sm(self) -> Union[int, str, None]: @@ -1576,6 +1671,17 @@ class BootstrapContainer(HTML.Div): @m.setter def m(self, value: Union[int, str, None]): self._set_css_param_number("m-", value) + + @property + def m_xs(self) -> Union[int, str, None]: + """ + element's margin for small screens. By default, bootstrap defines the values 0,1,2,3,4,5,auto + """ + return self._get_css_param_number("m-", "xs") + + @m_xs.setter + def m_xs(self, value: Union[int, str, None]): + self._set_css_param_number("m-", value, "xs") @property def m_sm(self) -> Union[int, str, None]: @@ -1642,6 +1748,17 @@ class BootstrapContainer(HTML.Div): @mx.setter def mx(self, value: Union[int, str, None]): self._set_css_param_number("mx-", value) + + @property + def mx_xs(self) -> Union[int, str, None]: + """ + element's margin-left and margin-right for small screens. By default, bootstrap defines the values 0,1,2,3,4,5,auto + """ + return self._get_css_param_number("mx-", "xs") + + @mx_xs.setter + def mx_xs(self, value: Union[int, str, None]): + self._set_css_param_number("mx-", value, "xs") @property def mx_sm(self) -> Union[int, str, None]: @@ -1708,6 +1825,17 @@ class BootstrapContainer(HTML.Div): @my.setter def my(self, value: Union[int, str, None]): self._set_css_param_number("my-", value) + + @property + def my_xs(self) -> Union[int, str, None]: + """ + element's margin-top and margin-bottom for small screens. By default, bootstrap defines the values 0,1,2,3,4,5,auto + """ + return self._get_css_param_number("my-", "xs") + + @my_xs.setter + def my_xs(self, value: Union[int, str, None]): + self._set_css_param_number("my-", value, "xs") @property def my_sm(self) -> Union[int, str, None]: @@ -1774,6 +1902,17 @@ class BootstrapContainer(HTML.Div): @mt.setter def mt(self, value: Union[int, str, None]): self._set_css_param_number("mt-", value) + + @property + def mt_xs(self) -> Union[int, str, None]: + """ + element's margin-top for small screens. By default, bootstrap defines the values 0,1,2,3,4,5,auto + """ + return self._get_css_param_number("mt-", "xs") + + @mt_xs.setter + def mt_xs(self, value: Union[int, str, None]): + self._set_css_param_number("mt-", value, "xs") @property def mt_sm(self) -> Union[int, str, None]: @@ -1840,6 +1979,17 @@ class BootstrapContainer(HTML.Div): @mb.setter def mb(self, value: Union[int, str, None]): self._set_css_param_number("mb-", value) + + @property + def mb_xs(self) -> Union[int, str, None]: + """ + element's margin-bottom for small screens. By default, bootstrap defines the values 0,1,2,3,4,5,auto + """ + return self._get_css_param_number("mb-", "xs") + + @mb_xs.setter + def mb_xs(self, value: Union[int, str, None]): + self._set_css_param_number("mb-", value, "xs") @property def mb_sm(self) -> Union[int, str, None]: @@ -1907,6 +2057,17 @@ class BootstrapContainer(HTML.Div): def ms(self, value: Union[int, str, None]): self._set_css_param_number("ms-", value) + @property + def ms_xs(self) -> Union[int, str, None]: + """ + element's start margin (left for LTR languages) for small screens. By default, bootstrap defines the values 0,1,2,3,4,5,auto + """ + return self._get_css_param_number("ms-", "xs") + + @ms_xs.setter + def ms_xs(self, value: Union[int, str, None]): + self._set_css_param_number("ms-", value, "xs") + @property def ms_sm(self) -> Union[int, str, None]: """ @@ -1972,6 +2133,17 @@ class BootstrapContainer(HTML.Div): @me.setter def me(self, value: Union[int, str, None]): self._set_css_param_number("me-", value) + + @property + def me_xs(self) -> Union[int, str, None]: + """ + element's end margin (right for LTR languages) for small screens. By default, bootstrap defines the values 0,1,2,3,4,5,auto + """ + return self._get_css_param_number("me-", "xs") + + @me_xs.setter + def me_xs(self, value: Union[int, str, None]): + self._set_css_param_number("me-", value, "xs") @property def me_sm(self) -> Union[int, str, None]: @@ -2041,6 +2213,17 @@ class BootstrapContainer(HTML.Div): def g(self, value: Union[int, str, None]): self._set_css_param_number("g-", value) + @property + def g_xs(self) -> Union[int, str, None]: + """ + gutter (gutters are the space between the element and its content) for small screens. By default, bootstrap defines the values 0,1,2,3,4,5 + """ + return self._get_css_param_number("g-", "xs") + + @g_xs.setter + def g_xs(self, value: Union[int, str, None]): + self._set_css_param_number("g-", value, "xs") + @property def g_sm(self) -> Union[int, str, None]: """ @@ -2107,6 +2290,17 @@ class BootstrapContainer(HTML.Div): def gx(self, value: Union[int, str, None]): self._set_css_param_number("gx-", value) + @property + def gx_xs(self) -> Union[int, str, None]: + """ + horizontal gutter (gutters are the space between the element and its content) for small screens. By default, bootstrap defines the values 0,1,2,3,4,5 + """ + return self._get_css_param_number("gx-", "xs") + + @gx_xs.setter + def gx_xs(self, value: Union[int, str, None]): + self._set_css_param_number("gx-", value, "xs") + @property def gx_sm(self) -> Union[int, str, None]: """ @@ -2173,6 +2367,17 @@ class BootstrapContainer(HTML.Div): def gy(self, value: Union[int, str, None]): self._set_css_param_number("gy-", value) + @property + def gy_xs(self) -> Union[int, str, None]: + """ + vertical gutter (gutters are the space between the element and its content) for small screens. By default, bootstrap defines the values 0,1,2,3,4,5 + """ + return self._get_css_param_number("gy-", "xs") + + @gy_xs.setter + def gy_xs(self, value: Union[int, str, None]): + self._set_css_param_number("gy-", value, "xs") + @property def gy_sm(self) -> Union[int, str, None]: """ @@ -2240,6 +2445,17 @@ class BootstrapContainer(HTML.Div): @col.setter def col(self, value: Union[int, str, None]): self._set_css_param_number("col-", value) + + @property + def col_xs(self) -> Union[int, str, None]: + """ + column class for very small screens. By default, bootstrap defines the values 1,2,3,4,5,6,7,8,9,10,11,12 + """ + return self._get_css_param_number("col-", "xs") + + @col_xs.setter + def col_xs(self, value: Union[int, str, None]): + self._set_css_param_number("col-", value, "xs") @property def col_sm(self) -> Union[int, str, None]: diff --git a/pyscript_bootstrap_templates/bootstrap_templates.py b/pyscript_bootstrap_templates/bootstrap_templates.py index 619b817..bc50065 100644 --- a/pyscript_bootstrap_templates/bootstrap_templates.py +++ b/pyscript_bootstrap_templates/bootstrap_templates.py @@ -10,20 +10,112 @@ class PyScriptBootstrapApp(object): self._main_div.vh_100 = True self._main_div.p = 0 + self._main_div.m = 0 + + self._main_div.col_xs = 12 + self._main_div.col = 12 + + self._parent_element = document.getElementById(parent_element) self._parent_element.appendChild(self._main_div.element) + self._toast_container = bHTML.ToastContainer(parent=self._main_div) + self._toast_container.position_end = 0 + self._toast_container.position_bottom = 0 + + self._alert_container = bHTML.BootstrapContainer(parent=self._main_div) + self._alert_container.position_end = 0 + self._alert_container.position_bottom = 0 + self._alert_container.position = bHTML.Position.ABSOLUTE + + def _alert(self, message: str, alert_class: type): + return alert_class(message, parent=self._alert_container) + bHTML.Al + @property def main(self) -> HTML.Div: return self._main_div + + @property + def toast_container(self) -> bHTML.ToastContainer: + return self._toast_container + + @property + def alert_container(self) -> bHTML.BootstrapContainer: + return self._alert_container + + def toast(self, message: str, title: str, animation: bool = True, show: bool = True) -> bHTML.Toast: + """ + show a toast on the default toast location (bottom right) + """ + toast = bHTML.Toast(inner_html=message, title=title, parent=self._toast_container) + toast.animation = animation + if show: + toast.show() + return toast + + def alert(self, message:str) -> bHTML.Alert: + """ + show an alert on the default alert location (bottom right) + """ + return self._alert(message, bHTML.Alert) + + def alert_success(self, message:str) -> bHTML.AlertSuccess: + """ + show an alert on the default alert location (bottom right) + """ + return self._alert(message, bHTML.AlertSuccess) + + def alert_info(self, message:str) -> bHTML.AlertInfo: + """ + show an alert on the default alert location (bottom right) + """ + return self._alert(message, bHTML.AlertInfo) + + def alert_warning(self, message:str) -> bHTML.AlertWarning: + """ + show an alert on the default alert location (bottom right) + """ + return self._alert(message, bHTML.AlertWarning) + + def alert_danger(self, message:str) -> bHTML.AlertDanger: + """ + show an alert on the default alert location (bottom right) + """ + return self._alert(message, bHTML.AlertDanger) + + def alert_primary(self, message:str) -> bHTML.AlertPrimary: + """ + show an alert on the default alert location (bottom right) + """ + return self._alert(message, bHTML.AlertPrimary) + + def alert_secondary(self, message:str) -> bHTML.AlertSecondary: + """ + show an alert on the default alert location (bottom right) + """ + return self._alert(message, bHTML.AlertSecondary) + + def alert_light(self, message:str) -> bHTML.AlertLight: + """ + show an alert on the default alert location (bottom right) + """ + return self._alert(message, bHTML.AlertLight) + + def alert_dark(self, message:str) -> bHTML.AlertDark: + """ + show an alert on the default alert location (bottom right) + """ + return self._alert(message, bHTML.AlertDark) class PyScriptBootstrapDashboard(PyScriptBootstrapApp): def __init__(self, parent_element:str = "pyscript_app", brand_name = "Dashboard"): super().__init__(parent_element) - self._sidebar = bHTML.Col(id="sidebar", col_sm=5, col_md=4, col_lg=3, col_xl=3) + self._sidebar = bHTML.Col(id="sidebar", col=12, col_sm=12, col_md=4, col_lg=3, col_xl=3) + self._sidebar.style = {"height": "100vh", "overflow": "auto"} self._navbar = bHTML.NavbarDark( parent=self._main_div, @@ -32,13 +124,30 @@ class PyScriptBootstrapDashboard(PyScriptBootstrapApp): toggle_button_for_target=self.sidebar ) - self._navbar.position = bHTML.Position.STICKY_TOP - self._navbar.w = 100 + self._navbar.position = bHTML.Position.FIXED_TOP + self._navbar.height = "4em" + self._navbar.ps = 5 + self._navbar.pe = 5 + self._navbar.col = 12 + + + top_margin_dummy = bHTML.BootstrapContainer(id="top_margin_dummy", parent=self._main_div) + top_margin_dummy.height = "3.5em" # have it slightly less high than navbar + top_margin_dummy.width = "100vw" + top_margin_dummy.m = 0 + top_margin_dummy.p = 0 row = bHTML.Row(parent=self._main_div) - row.h = 100 row.w = 100 + + row.height = "calc(100vh - 4em)" + row.p = 0 + row.m = 0 + row.col_xs = 12 + row.col = 12 + + row.display_property = bHTML.DisplayProperty.INLINE_FLEX row.append_child(self.sidebar) @@ -49,17 +158,19 @@ class PyScriptBootstrapDashboard(PyScriptBootstrapApp): self._sidebar.position = bHTML.Position.STATIC self._sidebar.collapsable = True self._sidebar.mw = 100 + self._sidebar.height = "calc(100vh - 4em)" self._sidebar.shadow = bHTML.Shadow.LARGE - self._sidebar.overflow = bHTML.Overflow.HIDDEN + self._sidebar.overflow = bHTML.Overflow.SCROLL self._modal = bHTML.Modal(parent=self._main_div, title="Modal") - self._main_area = bHTML.Col(id="main_area", parent=row, col_lg=9, col_xl=9) + self._main_area = bHTML.Col(id="main_area", parent=row, col=12, col_sm=12, col_md=8, col_lg=9, col_xl=9) + self._main_area.p = 4 - self._main_area.overflow = bHTML.Overflow.HIDDEN - #self._main_area.vh_100 = True + self._main_area.overflow = bHTML.Overflow.SCROLL + self._main_area.height = "calc(100vh - 4em)" self._main_area.mw = 100 def show_modal(self): diff --git a/pyscript_bootstrap_templates/cli_interface.py b/pyscript_bootstrap_templates/cli_interface.py index aae48f3..7e291d0 100644 --- a/pyscript_bootstrap_templates/cli_interface.py +++ b/pyscript_bootstrap_templates/cli_interface.py @@ -106,6 +106,17 @@ self.addEventListener('fetch', event => {{ return response; }})()); +}}); +self.addEventListener('activate', (event) => {{ + event.waitUntil( + caches.keys().then((keyList) => {{ + return Promise.all(keyList.map((key) => {{ + if(key !== pwa_version) {{ + return caches.delete(key); + }} + }})); + }}) + ); }}); """ @@ -306,7 +317,7 @@ def main(): argument_parser.add_argument("--bootstrap-js-url", type=str, help="the url of the bootstrap js file", default="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/js/bootstrap.bundle.min.js") argument_parser.add_argument("--pyscript-bootstrap-templates-wheel-url", type=str, - help="the url of the pyscript bootstrap templates wheel file", default="https://the-cake-is-a-lie.net/gogs/jonas/pyscript-bootstrap-templates/raw/branch/main/dist/pyscript_bootstrap_templates-0.1.0-py3-none-any.whl") + help="the url of the pyscript bootstrap templates wheel file", default="https://the-cake-is-a-lie.net/gogs/jonas/pyscript-bootstrap-templates/raw/branch/main/dist/pyscript_bootstrap_templates-0.2.0-py3-none-any.whl") argument_parser.add_argument("--pwa-bg-color", type=str, help="background color for pwa configuration", default="#000000") argument_parser.add_argument("--pwa-theme-color", type=str, help="theme color for pwa configuration", default="#ffffff")