From 84382437822902296a90cc5685d859e7e53a0c27 Mon Sep 17 00:00:00 2001 From: KyMAN <0kyman0@gmail.com> Date: Wed, 26 Mar 2025 14:59:24 +0100 Subject: [PATCH] feat(py-wasm): Attributes and Components module done. --- Public/ecma/WASM/Python.ecma.js | 166 ++++++++++++++++-- Public/py/Abstracts/AnPMap.py | 134 +++++++++++++- Public/py/Abstracts/PyodideMap.py | 35 ++++ Public/py/Application/AnP.py | 8 +- Public/py/Application/Attributes.py | 107 +++++++++++ Public/py/Application/Components.py | 78 ++++++++ .../py/Drivers/PyodideJavaScriptInterface.py | 9 +- Public/py/Utils/Patterns.py | 4 +- Public/py/Utils/Utils.py | 7 +- 9 files changed, 521 insertions(+), 27 deletions(-) create mode 100644 Public/py/Abstracts/PyodideMap.py create mode 100644 Public/py/Application/Attributes.py create mode 100644 Public/py/Application/Components.py diff --git a/Public/ecma/WASM/Python.ecma.js b/Public/ecma/WASM/Python.ecma.js index 0829e14..fb55c36 100644 --- a/Public/ecma/WASM/Python.ecma.js +++ b/Public/ecma/WASM/Python.ecma.js @@ -33,6 +33,15 @@ * @returns {void} */ +/** + * @callback python_wasm_preload_callback + * @param {HTMLElement|null} item + * @param {boolean} asynchronous + * @param {number} error + * @param {Array.} error_messages + * @returns {void} + */ + /** * @class * @constructor @@ -92,7 +101,9 @@ export const PythonWASM = (function(){ /** @type {string} */ gui_mode = PythonWASM.get_value("gui_mode", inputs, "default"), /** @type {number} */ - ajax_timeout = PythonWASM.get_value("ajax_timeout", inputs, 2000); + ajax_timeout = PythonWASM.get_value(["ajax_timeout", "timeout"], inputs, 2000), + /** @type {number} */ + preload_timeout = PythonWASM.get_value(["preload_timeout", "timeout"], inputs, 2000); /** @type {PyodideAPI|null} */ this.pyodide = null; @@ -133,10 +144,22 @@ export const PythonWASM = (function(){ null)).filter(file => file); (async function(){ + self._print("info", "Building the Pyodide module..."); self.pyodide = await loadPyodide(); - self.pyodide.globals.set("load_file", self.load_file); - self.pyodide.globals.set("synchronous_post", self.synchronous_post); + + ["load_file", "synchronous_post", "preload_html_item"].forEach(parameters => { + + /** @type {[string|Array., string]} */ + const [names, key] = PythonWASM.is_array(parameters) ? parameters : [parameters, parameters]; + + PythonWASM.get_keys(names).forEach(name => { + self._print("info", "Implementing the '{key}' action interface.", {key : name}); + self.pyodide.globals.set(name, self[key]); + }); + + }); + self._print("info", "Loading the Python files..."); self.pyodide.runPython(` from pyodide.http import pyfetch @@ -149,6 +172,7 @@ export const PythonWASM = (function(){ }); PythonWASM.execute(callback, self); }); + })(); }; @@ -319,12 +343,13 @@ export const PythonWASM = (function(){ /** * @param {!string} url * @param {?python_wasm_ajax_callback} [callback = null] + * @param {?(Object.|Array.)} [inputs = null] * @param {!number} [options = 0] * @returns {XMLHttpRequest} * @access public */ - this.load_file = (url, callback = null, options = 0) => { - // console.log(url); + this.load_file = (url, callback = null, inputs = null, options = 0) => { + inputs = PythonWASM.get_default_inputs(inputs); /** @type {boolean} */ let ended = false; @@ -332,23 +357,24 @@ export const PythonWASM = (function(){ const ajax = new XMLHttpRequest(), /** @type {number} */ date = Date.now(), + /** @type {number} */ + timeout = PythonWASM.get_value(["ajax_timeout", "timeout"], inputs, ajax_timeout), /** * @param {!string} message * @returns {void} */ end = message => { - // !ended && console.log([callback, ajax.responseText, ajax.status, ajax.readyState, message == "OK", message]); !ended && (ended = true) && PythonWASM.execute(callback, ajax.responseText, ajax.status, ajax.readyState, message == "OK", message); }; ajax.open("get", url, true); - ajax.timeout = ajax_timeout; + ajax.timeout = timeout; ajax.onreadystatechange = () => { if(ended) return; if(ajax.readyState == 4) end([301, 302, 304].includes(ajax.status) || (ajax.status >= 200 && ajax.status < 300) ? "OK" : "HTTP_ERROR"); - else if(Date.now() - date > ajax_timeout) + else if(Date.now() - date > timeout) end("FORCED_TIMOUT"); }; ajax.send(null); @@ -364,11 +390,13 @@ export const PythonWASM = (function(){ * @param {!string} url * @param {?Object.} [variables = null] * @param {?python_wasm_ajax_callback} [callback = null] + * @param {?(Object.|Array.)} [inputs = null] * @param {!number} [options = 0] * @returns {XMLHttpRequest} * @access public */ - this.synchronous_post = (url, variables = null, callback = null, options = 0) => { + this.synchronous_post = (url, variables = null, callback = null, inputs = null, options = 0) => { + inputs = PythonWASM.get_default_inputs(inputs); /** @type {boolean} */ let ended = false, @@ -378,6 +406,8 @@ export const PythonWASM = (function(){ const ajax = new XMLHttpRequest(), /** @type {number} */ date = Date.now(), + /** @type {number} */ + timeout = PythonWASM.get_value(["ajax_timeout", "timeout"], inputs, ajax_timeout), /** * @param {!string} message * @returns {void} @@ -391,14 +421,14 @@ export const PythonWASM = (function(){ variables_uri += (variables_uri ? "&" : "") + encodeURIComponent(key) + "=" + encodeURIComponent(variables[key]); ajax.open("get", url, true); - ajax.timeout = ajax_timeout; + ajax.timeout = timeout; ajax.setRequestHeader("Content-type", "application/x-www-form-urlencoded;charset=UTF-8") ajax.onreadystatechange = () => { if(ended) return; if(ajax.readyState == 4) end([301, 302, 304].includes(ajax.status) || (ajax.status >= 200 && ajax.status < 300) ? "OK" : "HTTP_ERROR"); - else if(Date.now() - date > ajax_timeout) + else if(Date.now() - date > timeout) end("FORCED_TIMOUT"); }; ajax.send(variables_uri); @@ -410,6 +440,72 @@ export const PythonWASM = (function(){ return ajax; }; + /** + * @param {!(string|HTMLElement)} selector + * @param {?python_wasm_preload_callback} [callback = null] + * @param {?(Object.|Array.)} [inputs = null] + * @param {!number} [options = 0] + * @returns {Array.} + * @access public + */ + this.preload_html_item = (selector, callback = null, inputs = null, options = 0) => { + inputs = PythonWASM.get_default_inputs(inputs); + + /** @type {boolean} */ + let is_html_item = selector && (selector.tagName || selector.nodeName), + /** @type {HTMLElement|null} */ + item = is_html_item ? selector : null, + /** @type {number} */ + error = ( + (( + callback === undefined ? 1 << 0 : + callback === null ? 1 << 1 : + typeof callback != "function" ? 1 << 2 : + 0) << 0) | + (( + selector === undefined ? 1 << 0 : + selector === null ? 1 << 1 : + typeof selector != "string" && !is_html_item ? 1 << 2 : + 0) << 3) | + 0) << 1, + /** @type {boolean} */ + done = is_html_item; + + if(!done && !error){ + + try{ + done = !!(item = document.querySelector(selector)); + }catch(exception){ + error |= 1 << 7; + done = true; + }; + + if(!done){ + + /** @type {number} */ + const date = Date.now(), + /** @type {number} */ + timeout = Utils.get_value(["preload_timeout", "timeout"], inputs, preload_timeout), + /** @type {number} */ + interval = setInterval(() => { + if(item = document.querySelector(selector)){ + clearInterval(interval); + PythonWASM.execute(callback, item, true, error, PythonWASM.PRELOAD_ITEM_HTML_ERROR_MESSAGES); + }else if(Date.now() - date > timeout){ + clearInterval(interval); + PythonWASM.execute(callback, item, true, error |= 1 << 8, PythonWASM.PRELOAD_ITEM_HTML_ERROR_MESSAGES) + }; + }, 1000 / 24); + + }; + + }; + + done && PythonWASM.execute(callback, false, error, PythonWASM.PRELOAD_ITEM_HTML_ERROR_MESSAGES); + + return error; + }; + constructor(); }; @@ -428,6 +524,7 @@ export const PythonWASM = (function(){ ["Modules/ErrorsManager.py", "https://errorsmanager." + PythonWASM.DOMAIN + "/py/ErrorsManager.py"], "/py/common.py", "/py/Abstracts/AnPMap.py", + "/py/Abstracts/PyodideMap.py", "/py/Utils/Patterns.py", "/py/Utils/Options.py", "/py/Utils/Check.py", @@ -446,9 +543,24 @@ export const PythonWASM = (function(){ "/py/Managers/RandomKeys.py", "/py/Models/Thread.py", "/py/Managers/Threads.py", + "/py/Application/Attributes.py", + "/py/Application/Components.py", // "/py/run.py" ]; + /** @type {Array.} */ + PythonWASM.PRELOAD_ITEM_HTML_ERROR_MESSAGES = [ + "exception", + "callback_undefined", + "callback_null", + "callback_not_function", + "selector_undefined", + "selector_null", + "selector_bad_type", + "selector_bad_format", + "timeout" + ]; + /** * @param {?any} item * @returns {boolean} @@ -497,6 +609,22 @@ export const PythonWASM = (function(){ */ PythonWASM.is_function = item => typeof item == "function"; + /** + * @param {?any} item + * @returns {boolean} + * @access public + * @static + */ + PythonWASM.is_integer = item => typeof item == "number" && item >> 0 == item; + + /** + * @param {?any} item + * @returns {boolean} + * @access public + * @static + */ + PythonWASM.is_json_item = item => PythonWASM.is_dictionary(item) || PythonWASM.is_array(item); + /** * @param {?any} item * @returns {Array.} @@ -712,7 +840,10 @@ export const PythonWASM = (function(){ {object_name}:AnP = AnP({ "pyodide_javascript_interface_methods" : { - "load_file" : load_file + "load_file" : load_file, + "post" : synchronous_post, + "post_synchronous" : synchronous_post, + "preload_html_item" : preload_html_item } }) @@ -720,5 +851,16 @@ export const PythonWASM = (function(){ object_name : "anp" }, inputs]); + /** + * @param {?(Object.|Array.)} inputs + * @returns {Object.|Array.|null} + * @access public + * @static + */ + PythonWASM.get_default_inputs = inputs => ( + PythonWASM.is_integer(inputs) ? {timeout : inputs} : + PythonWASM.is_json_item(inputs) ? inputs : + null); + return PythonWASM; })(); \ No newline at end of file diff --git a/Public/py/Abstracts/AnPMap.py b/Public/py/Abstracts/AnPMap.py index 6c25509..11cb2eb 100644 --- a/Public/py/Abstracts/AnPMap.py +++ b/Public/py/Abstracts/AnPMap.py @@ -4,10 +4,47 @@ from Modules.ErrorsManager import ErrorsManager from typing import Self, Optional, Any, Callable from Utils.Options import Options +from re import Match as REMatch from typing import NewType +from sys import platform as SYS_PLATFORM AnP = NewType("AnP", Any) +is_emcripten:bool = SYS_PLATFORM == "emscripten" + +if is_emcripten: + + class ClassList: + + def contains(self:Self, kebab:str) -> bool:pass + + def add(self:Self, kebab:str) -> None:pass + + def remove(self:Self, kebab:str) -> None:pass + + class Style:pass + + class DOM: + + def querySelector(selector:str) -> Self|None:pass + + def querySelectorAll(selector:str) -> list[Self]:pass + + def remove() -> None:pass + + def hasAttribute(name:str) -> bool:pass + + def getAttribute(name:str) -> str:pass + + def setAttribute(name:str, value:Any|None) -> None:pass + + class HTMLElement(DOM, Style): + + def __init__(self): + self.style:Style = None + + class HTMLBodyElement(HTMLElement):pass + class BaseAbstract: def __init__(self:Self, anp:AnP, key:str, inputs:Optional[dict[str, Any|None]|list[Any|None]|tuple[Any|None]] = None) -> None: @@ -34,18 +71,22 @@ class BaseAbstract: def is_started(self:Self) -> bool:pass -class PyodideJavaScriptInterface(BaseAbstract): +if is_emcripten: - def __init__(self:Self, anp:AnP, inputs:Optional[dict[str, Any|None]|tuple[Any|None]|list[Any|None]] = None) -> None: - self.load_file:Callable[[str, Callable[[str|None, int, int, bool, str], str|None], int], tuple[str|None, int, bool, int]] = None - self.post:Callable[[str, Callable[[str|None, int, int, bool, str], str|None], int], tuple[str|None, int, bool, int]] = None - self.instances_threads:Callable[[int], bool] = None + class PyodideJavaScriptInterface(BaseAbstract): - def _set_basics(self:Self, inputs:Optional[dict[str, Any|None]|tuple[Any|None]|list[Any|None]] = None):pass + def __init__(self:Self, anp:AnP, inputs:Optional[dict[str, Any|None]|tuple[Any|None]|list[Any|None]] = None) -> None: + self.load_file:Callable[[str, Optional[Callable[[str|None, int, int, bool, str], str|None]], Optional[dict[str, Any|None]|tuple[Any|None]|list[Any|None]], int], tuple[str|None, int, bool, int]] = None + self.post:Callable[[str, dict[str|Any|None]|str, Optional[Callable[[str|None, int, int, bool, str], str|None]], Optional[dict[str, Any|None]|tuple[Any|None]|list[Any|None]], int], tuple[str|None, int, bool, int]] = None + self.post_synchronous:Callable[[str, dict[str|Any|None]|str, Optional[Callable[[str|None, int, int, bool, str], str|None]], Optional[dict[str, Any|None]|tuple[Any|None]|list[Any|None]], int], tuple[str|None, int, bool, int]] = None + self.preload_html_item:Callable[[str|HTMLElement, Optional[Callable[[HTMLElement|None, bool, int, list[str]], None]], int], tuple[HTMLElement|None, bool, int, list[str]]] = None + self.instances_threads:Callable[[int], bool] = None - def _build(self:Self, inputs:Optional[dict[str, Any|None]|tuple[Any|None]|list[Any|None]] = None) -> None:pass + def _set_basics(self:Self, inputs:Optional[dict[str, Any|None]|tuple[Any|None]|list[Any|None]] = None):pass - def add(self:Self, inputs:dict[str, Callable[[tuple[Any|None]], Any|None]]|list[Any|None]|tuple[Any|None], options:int = 0) -> int:pass + def _build(self:Self, inputs:Optional[dict[str, Any|None]|tuple[Any|None]|list[Any|None]] = None) -> None:pass + + def add(self:Self, inputs:dict[str, Callable[[tuple[Any|None]], Any|None]]|list[Any|None]|tuple[Any|None], options:int = 0) -> int:pass class Console: @@ -130,7 +171,7 @@ class JSONLoader: end_callback:Optional[Callable[[], None]] = None, only_dictionaries:bool = False, options:int = 0 - ) -> None: + ): self.anp:AnP = None self.has:Options = None self.has_console:bool = None @@ -364,6 +405,79 @@ class TerminalManager(BaseAbstract): def _close_command(self:Self, dictionary:dict[str, Any|None], *arguments:list[Any|None]) -> None:pass +class CommonAttributes(BaseAbstract): + + def __init__(self:Self, anp:AnP, inputs:Optional[dict[str, Any|None]|tuple[Any|None]|tuple[Any|None]] = None) -> None: + self.standard_names:list[str] = None + + @staticmethod + def set_names_block(names:str|list[str]|tuple[str]|None) -> list[str]|None:pass + + @staticmethod + def check(name:str, exclude:list[str]|tuple[str]|None, only:Optional[list[str]|tuple[str]] = None) -> bool:pass + + @staticmethod + def set_match_group(matches:REMatch) -> str:pass + + def name_format(self:Self, name:str) -> str:pass + + def set_string(self:Self, + attributes:dict[str, Any|None], + exclude:Optional[str|list[str]|tuple[str]] = None, + only:Optional[str|list[str]|tuple[str]] = None + ) -> str:pass + +if is_emcripten: + + class BaseAttributes(CommonAttributes): + + def set(self:Self, + item:HTMLElement|None, + attributes:dict[str, Any|None], + exclude:Optional[str|list[str]|tuple[str]] = None, + only:Optional[str|list[str]|tuple[str]] = None + ) -> str|None:pass + +else: + + class BaseAttributes(CommonAttributes): + + def set(self:Self, + _:Any|None, + attributes:dict[str, Any|None], + exclude:Optional[str|list[str]|tuple[str]] = None, + only:Optional[str|list[str]|tuple[str]] = None + ) -> str|None:pass + +class Attributes(BaseAttributes):pass + +if is_emcripten: + + class BaseComponents(BaseAbstract): + + def __init__(self:Self, anp:AnP, inputs:Optional[dict[str, Any|None]|tuple[Any|None]|list[Any|None]] = None) -> None:pass + + def preload(self:Self, + selector:HTMLElement|None, + callback:Optional[Callable[[HTMLElement|None, bool, int, list[str]], None]] = None, + inputs:Optional[dict[str, Any|None]|tuple[Any|None]|list[Any|None]] = None, + options:int = 0 + ) -> tuple[HTMLElement|None, bool, int, list[str]]:pass + +else: + + class BaseComponents(BaseAbstract): + + def __init__(self:Self, anp:AnP, inputs:Optional[dict[str, Any|None]|tuple[Any|None]|list[Any|None]] = None) -> None:pass + +class Components(BaseComponents): + + def set(self:Self, items:list[Any|None]|tuple[Any|None]) -> str:pass + + def i18n(self:Self, i18n:str, tag:str = "span", inputs:Optional[str|dict[str, Any|None]|tuple[Any|None]|list[Any|None]] = None) -> str:pass + + def icon(self:Self, icon:str, tag:str = "span", inputs:Optional[str|dict[str, Any|None]|tuple[Any|None]|list[Any|None]] = None) -> str:pass + class AnP: def __init__(self:Self, inputs:Optional[dict[str, Any|None]|list[Any|None]|tuple[Any|None]] = None) -> None: @@ -376,6 +490,8 @@ class AnP: self.i18n:I18NManager = None self.random_keys:RandomKeysManager = None self.threads:ThreadsManager = None + self.attributes:Attributes = None + self.components:Components = None self.terminal:TerminalManager = None def set_subbasics(self:Self, inputs:Optional[dict[str, Any|None]|list[Any|None]|tuple[Any|None]] = None) -> None:pass diff --git a/Public/py/Abstracts/PyodideMap.py b/Public/py/Abstracts/PyodideMap.py new file mode 100644 index 0000000..96eda10 --- /dev/null +++ b/Public/py/Abstracts/PyodideMap.py @@ -0,0 +1,35 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +from common import Self, Any + +class ClassList: + + def contains(self:Self, kebab:str) -> bool:None + + def add(self:Self, kebab:str) -> None:None + + def remove(self:Self, kebab:str) -> None:None + +class Style:pass + +class DOM: + + def querySelector(selector:str) -> Self|None:pass + + def querySelectorAll(selector:str) -> list[Self]:pass + + def remove() -> None:pass + + def hasAttribute(name:str) -> bool:pass + + def getAttribute(name:str) -> str:pass + + def setAttribute(name:str, value:Any|None) -> None:pass + +class HTMLElement(DOM, Style): + + def __init__(self:Self) -> None: + self.style:Style = None + +class HTMLBodyElement(HTMLElement):pass \ No newline at end of file diff --git a/Public/py/Application/AnP.py b/Public/py/Application/AnP.py index 6531d62..812ca99 100644 --- a/Public/py/Application/AnP.py +++ b/Public/py/Application/AnP.py @@ -13,6 +13,8 @@ from Managers.Settings import SettingsManager from Managers.I18N import I18NManager from Managers.RandomKeys import RandomKeysManager from Managers.Threads import ThreadsManager +from Application.Attributes import Attributes +from Application.Components import Components from Utils.Check import Check if not Check.is_emscripten(): @@ -71,6 +73,8 @@ class AnP: self.i18n:I18NManager = I18NManager(self, inputs) self.random_keys:RandomKeysManager = RandomKeysManager(self, inputs) self.threads:ThreadsManager = ThreadsManager(self, inputs) + self.attributes:Attributes = Attributes(self, inputs) + self.components:Components = Components(self, inputs) if not is_emscripten: self.terminal:TerminalManager = TerminalManager(self, inputs) @@ -127,7 +131,8 @@ class AnP: try: Utils.execute_asynchronous(( ("console", "json", "pyodide", "request", "settings", "i18n", "random_keys", "threads") + - (tuple() if is_emscripten else ("terminal",)) + (tuple() if is_emscripten else ("terminal",)) + + ("attributes", "components") ), self.__start_module, lambda:Utils.execute(callback, True)) except Exception as exception: self.__error |= 1 << 1 @@ -168,6 +173,7 @@ class AnP: try: Utils.execute_asynchronous(( + ("components", "attributes") + (tuple() if is_emscripten else ("terminal",)) + ("i18n", "settings", "request", "pyodide", "json", "console") ), self.__close_module, lambda:Utils.execute(callback, True)) diff --git a/Public/py/Application/Attributes.py b/Public/py/Application/Attributes.py new file mode 100644 index 0000000..2d05cac --- /dev/null +++ b/Public/py/Application/Attributes.py @@ -0,0 +1,107 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +from Abstracts.Base import BaseAbstract +from Abstracts.AnPMap import AnP +from common import Self, Optional, Any, REMatch +from Utils.Check import Check +from Utils.Utils import Utils +from Utils.Patterns import Patterns + +class CommonAttributes(BaseAbstract): + + def __init__(self:Self, anp:AnP, inputs:Optional[dict[str, Any|None]|tuple[Any|None]|tuple[Any|None]] = None) -> None: + super().__init__(anp, "attributes", inputs) + + self.standard_names:list[str] = [] + + @staticmethod + def set_names_block(names:str|list[str]|tuple[str]|None) -> list[str]|None: + + array:list[str] = Utils.get_selector_keys(names) + + return array if len(array) else None + + @staticmethod + def check(name:str, exclude:list[str]|tuple[str]|None, only:Optional[list[str]|tuple[str]] = None) -> bool: + return (not exclude or name not in exclude) and (not only or name in only) + + @staticmethod + def set_match_group(matches:REMatch) -> str: + return "-" + (str(matches.group(1)).lower() if matches.group(1) else "") + + def name_format(self:Self, name:str) -> str: + + name = Patterns.RE_TO_KEBAB.sub(name, CommonAttributes.set_match_group) + + return name if name in self.standard_names or name[:5] in ("data-", "aria-") else "data-" + name + + def set_string(self:Self, + attributes:dict[str, Any|None], + exclude:Optional[str|list[str]|tuple[str]] = None, + only:Optional[str|list[str]|tuple[str]] = None + ) -> str: + + styles:str = "" + + exclude = CommonAttributes.set_names_block(exclude) + only = CommonAttributes.set_names_block(only) + + for name, value in Utils.get_dictionary(attributes).items(): + if CommonAttributes.check(name): + styles += " " + self.name_format(name) + "=\"" + value + "\"" + + return styles + +if Check.is_emscripten(): + from Abstracts.PyodideMap import HTMLElement + + class BaseAttributes(CommonAttributes): + + def set(self:Self, + item:HTMLElement|None, + attributes:dict[str, Any|None], + exclude:Optional[str|list[str]|tuple[str]] = None, + only:Optional[str|list[str]|tuple[str]] = None + ) -> str|None: + + styles:str|None = None + + if Check.is_json_item(item): + (item, attributes, exclude, only) = (None, item, attributes, exclude) + + if item: + + name:str + value:Any|None + + exclude = CommonAttributes.set_names_block(exclude) + only = CommonAttributes.set_names_block(only) + + for name, value in Utils.get_dictionary(attributes).items(): + CommonAttributes.check(name) and item.setAttribute(self.name_format(name), value) + + else: + styles = self.set_string(attributes, exclude, only) + + return styles + +else: + + class BaseAttributes(CommonAttributes): + + def set(self:Self, + _:Any|None, + attributes:dict[str, Any|None], + exclude:Optional[str|list[str]|tuple[str]] = None, + only:Optional[str|list[str]|tuple[str]] = None + ) -> str|None: + + if Check.is_dictionary(_): + (_, attributes, exclude, only) = (None, _, attributes, exclude) + + return self.set_string(attributes, exclude, only) + + +class Attributes(BaseAttributes): + pass \ No newline at end of file diff --git a/Public/py/Application/Components.py b/Public/py/Application/Components.py new file mode 100644 index 0000000..60213aa --- /dev/null +++ b/Public/py/Application/Components.py @@ -0,0 +1,78 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +from Abstracts.Base import BaseAbstract +from Abstracts.AnPMap import AnP +from common import Self, Optional, Any, Callable +from Utils.Check import Check +from Utils.Utils import Utils + +if Check.is_emscripten(): + + from Abstracts.PyodideMap import HTMLElement + from js import document + + class BaseComponents(BaseAbstract): + + def __init__(self:Self, anp:AnP, inputs:Optional[dict[str, Any|None]|tuple[Any|None]|list[Any|None]] = None) -> None: + super().__init__(anp, "components", inputs) + + def preload(self:Self, + selector:HTMLElement|None, + callback:Optional[Callable[[HTMLElement|None, bool, int, list[str]], None]] = None, + inputs:Optional[dict[str, Any|None]|tuple[Any|None]|list[Any|None]] = None, + options:int = 0 + ) -> tuple[HTMLElement|None, bool, int, list[str]]: + return self.anp.pyodide.preload_html_item(selector, callback, inputs, options) + +else: + + class BaseComponents(BaseAbstract): + def __init__(self:Self, anp:AnP, inputs:Optional[dict[str, Any|None]|tuple[Any|None]|list[Any|None]] = None) -> None: + super().__init__(anp, "components", inputs) + +class Components(BaseComponents): + + def set(self:Self, items:list[Any|None]|tuple[Any|None]) -> str: + + html:str = '' + + if Check.is_array(items): + for item in items: + item = Utils.get_array(item) + + l:int = len(item) + + if l == 1 and Check.is_array(item[0]): + if len(item[0]) and Check.is_string(item[0][0]) and hasattr(self.anp.components, item[0][0]): + + method:Callable[[list[Any|None]], str] = getattr(self.anp.components, item[0][0]) + + if callable(method): + html += method(*item[0][1:]) + + else: + + tag:str = item[0] + attributes:dict[str, Any|None]|tuple[Any|None]|list[Any|None]|None = item[1] if l > 1 else None + childs:list[Any|None]|tuple[Any|None]|None = item[2] if l > 2 else None + + html += '<' + tag + self.anp.attributes.set(attributes) + '>' + self.set(childs) + '' + + elif Check.is_string(items): + html += items + + return html + + def i18n(self:Self, i18n:str, tag:str = "span", inputs:Optional[str|dict[str, Any|None]|tuple[Any|None]|list[Any|None]] = None) -> str: + + text:str = self.anp.i18n.get(i18n) + + return self.set([[tag, (inputs, { + "i18n" : i18n + }), text]]) + + def icon(self:Self, icon:str, tag:str = "span", inputs:Optional[str|dict[str, Any|None]|tuple[Any|None]|list[Any|None]] = None) -> str: + return self.set([[tag, (inputs, { + "icon" : icon + })]]) \ No newline at end of file diff --git a/Public/py/Drivers/PyodideJavaScriptInterface.py b/Public/py/Drivers/PyodideJavaScriptInterface.py index 7e45579..e762bc7 100644 --- a/Public/py/Drivers/PyodideJavaScriptInterface.py +++ b/Public/py/Drivers/PyodideJavaScriptInterface.py @@ -2,6 +2,7 @@ # -*- coding: utf-8 -*- from Abstracts.Base import BaseAbstract +from Abstracts.PyodideMap import HTMLElement from Abstracts.AnPMap import AnP from common import Self, Any, Callable, Optional from Utils.Options import Options @@ -27,7 +28,7 @@ class PyodideJavaScriptInterface(BaseAbstract): "default_pyodide_javascript_interface_methods", "default_javascript_interface_methods", "pyodide_javascript_interface_methods", "javascript_interface_methods" ): - self.add(get(key, inputs)[0]) + self.add(get(key, inputs)[0], Options.OVERWRITE) def _build(self:Self, inputs:Optional[dict[str, Any|None]|tuple[Any|None]|list[Any|None]] = None) -> None: @@ -40,8 +41,10 @@ class PyodideJavaScriptInterface(BaseAbstract): self.__show_add_error:bool = True self.__show_add_ok:bool = False - self.load_file:Callable[[str, Callable[[str|None, int, int, bool, str], str|None], int], tuple[str|None, int, bool, int]] = None - self.post:Callable[[str, Callable[[str|None, int, int, bool, str], str|None], int], tuple[str|None, int, bool, int]] = None + self.load_file:Callable[[str, Optional[Callable[[str|None, int, int, bool, str], str|None]], Optional[dict[str, Any|None]|tuple[Any|None]|list[Any|None]], int], tuple[str|None, int, bool, int]] = None + self.post:Callable[[str, dict[str|Any|None]|str, Optional[Callable[[str|None, int, int, bool, str], str|None]], Optional[dict[str, Any|None]|tuple[Any|None]|list[Any|None]], int], tuple[str|None, int, bool, int]] = None + self.post_synchronous:Callable[[str, dict[str|Any|None]|str, Optional[Callable[[str|None, int, int, bool, str], str|None]], Optional[dict[str, Any|None]|tuple[Any|None]|list[Any|None]], int], tuple[str|None, int, bool, int]] = None + self.preload_html_item:Callable[[str|HTMLElement, Optional[Callable[[HTMLElement|None, bool, int, list[str]], None]], int], tuple[HTMLElement|None, bool, int, list[str]]] = None self.instances_threads:Callable[[int], bool] = None def __init__(self:Self, anp:AnP, inputs:Optional[dict[str, Any|None]|tuple[Any|None]|list[Any|None]] = None) -> None: diff --git a/Public/py/Utils/Patterns.py b/Public/py/Utils/Patterns.py index c043109..e6a3c83 100644 --- a/Public/py/Utils/Patterns.py +++ b/Public/py/Utils/Patterns.py @@ -5,6 +5,7 @@ from common import REPattern, re_compile, RE_IGNORE_CASE class Patterns: RE_KEY:REPattern = re_compile(r'^[a-z0-9_]+$', RE_IGNORE_CASE) + RE_SELECTOR_KEY:REPattern = re_compile(r'^[a-z0-9_\-]+$', RE_IGNORE_CASE) RE_STRING_VARIABLES:REPattern = re_compile(r'\{([a-z0-9_]+)\}', RE_IGNORE_CASE) RE_COLOR_HEXADECIMAL:REPattern = re_compile(r'^\#([0-9a-f]{3}|([0-9a-f]{2}){3,4})$', RE_IGNORE_CASE) RE_COLOR_RGB:REPattern = re_compile(r'^rgba?\((' + @@ -21,4 +22,5 @@ class Patterns: RE_BREAK_LINES:REPattern = re_compile(r'\r\n|[\r\n]') RE_COMMAND_AND_PARAMETERS:REPattern = re_compile(r'^\s*([^\s]+)(\s+(.*))?$') RE_COMMAND_PARAMETER:REPattern = re_compile(r'((\-{,2})([a-z0-9_]+)\s*[\=\:]\s*("(([^\\\\"]+|\\.)*)"|\'(([^\\\\\']+|\\.)*)\'|([^\s]*)))|"(([^\\\\"]+|\\.)*)"|\'(([^\\\\\']+|\\.)*)\'|([^\s]+)', RE_IGNORE_CASE) - RE_SLASH_ESCAPE:REPattern = re_compile(r'\\(.)') \ No newline at end of file + RE_SLASH_ESCAPE:REPattern = re_compile(r'\\(.)') + RE_TO_KEBAB:REPattern = re_compile('([A-Z0-9]([A-Z0-9]*|[a-z0-9]*))|[^a-zA-Z0-9]+') \ No newline at end of file diff --git a/Public/py/Utils/Utils.py b/Public/py/Utils/Utils.py index 3a74f99..2c2e486 100644 --- a/Public/py/Utils/Utils.py +++ b/Public/py/Utils/Utils.py @@ -215,5 +215,10 @@ class Utils: return alphabet + @staticmethod def get_random_item(items:list[Any|None]|tuple[Any|None]) -> Any|None: - return get_random_item(items) \ No newline at end of file + return get_random_item(items) + + @staticmethod + def get_selector_keys(keys:Any|None) -> list[str]: + return [key for key in Utils.get_array(keys) if key and Check.is_string(key) and Patterns.RE_SELECTOR_KEY.match(key)] \ No newline at end of file