HollowBond/Public/md/base-dev.md

16 KiB

Tengo la siguiente base estructural hecha sobre la que trabajar. Para empezar, Tenemos el archivo principal de la aplicación, el cual es /Public/ecma/Application/HollowBond.ecma.js.

"use strict";

const HollowBond = (function(){

    const HollowBond = function(){

        const self = this, 
              screen = {x : 0, y : 0};
        let cells = 40, 
            thread_interval = null, 
            fps = 12;

        this.item_self = document;

        this.settings = new SettingsManager(self);
        this.i18n = new I18NManager(self);
        this.views = {};

        const constructor = () => {
            
            const position = self.settings.get("position", null, "body");
            let target, preload = setInterval(() => {
                if(target = document.querySelector(position)){
                    clearInterval(preload);

                    self.item_self = target.appendChild(document.createElement("div"));
                    self.item_self.className = self.item_self.id = self.settings.get("class_name", null, "hollow-bond");
                    self.item_self.setAttribute("data-cells", cells = self.settings.get("cells", null, cells));

                    thread_interval = setInterval(thread, 1000 / fps);

                    build();

                };
            }, fps = self.settings.get(["frames_per_second", "fps"], null, fps));

            Common.SCSS(SCSS);

        };

        const thread = () => {
            if(screen.x != self.item_self.offsetWidth || screen.y != self.item_self.offsetHeight){
                screen.x = self.item_self.offsetWidth;
                screen.y = self.item_self.offsetHeight;

                self.item_self.style.fontSize = ((screen.x < screen.y ? screen.x : screen.y) / cells) + "px";

            };
        };

        const build = () => {};

        /**
         * @param  {...(HTMLElement|string|Array.<any|null>)} inputs 
         * @returns {Array.<HTMLElement>}
         * @access public
         * @static
         */
        this.html = (...inputs) => {
            const fragment = new DocumentFragment(), 
                  master = (
                      inputs[0] && (inputs[0].tagName || inputs[0].nodeName) ? inputs[0] : 
                      typeof inputs[0] == "string" ? document.querySelector(inputs[0]) : 
                  null), 
                  items = [];
    
            (master ? inputs.slice(1) : inputs).forEach(subinputs => {
                if(subinputs && (subinputs.tagName || subinputs.nodeName))
                    fragment.appendChild(subinputs);
                else if(typeof subinputs == "string")
                    fragment.appendChild(document.createTextNode(subinputs));
                else if(subinputs instanceof Array){
                    const [tag, attributes, children] = subinputs.concat([null, null]).slice(0, 3), 
                          item = document.createElement(tag);
    
                    attributes && attributes.constructor == Object && Object.entries(attributes).forEach(([key, value]) => {
                        if(/^on[_\-]?/i.test(key) && typeof value == "function")
                            item.addEventListener(key.replace(/^on[_\-]?/i, "").replace(/[^a-z0-9]+/gi, "").toLowerCase(), event => {
                                value(item, event);
                            });
                        else if(value !== null)
                            item.setAttribute(key.replace(/[^a-z0-9]+/gi, "-"), value);
                    });
    
                    if(children instanceof Array)
                        self.html(...children).forEach(child => item.appendChild(child));
                    else if(typeof children == "string")
                        item.innerHTML += children;
                    
                    fragment.appendChild(item);
                };
            });
    
            items.push(...fragment.childNodes);
            master && master.appendChild(fragment);
    
            return items;
        };

        constructor();

    };

    return HollowBond;
})();

Dentro de este hay dos gestores, siendo el primero de ellos el de configuración /Public/ecma/Managers/SettingsManager.ecma.js:

"use strict";

const SettingsManager = (function(){

    const SettingsManager = function(hollow_bond){

        const self = this, 
              settings = {};

        const constructor = () => {
            self.add(settings, true);
        };

        this.add = (block, overwrite = true) => {
            for(const key in block)
                if(overwrite || settings[key] === undefined)
                    settings[key] = block[key];
        };

        this.get = (keys, inputs = null, _default = null) => Common.get_value(keys, [inputs, settings], _default);

        constructor();

    };

    return SettingsManager;
})();

Y el segundo el de gestión de textos e idiomas /Public/ecma/Managers/I18NManager.ecma.js:

"use strict";

const I18NManager = (function(){

    const I18NManager = function(hollow_bond){

        const self = this, 
              sentences = {};
        let language = "espanol";

        const constructor = () => {
            [i18n, typeof custom_i18n != "undefined" ? custom_i18n : {}].forEach(block => {
                self.add(block, true);
            });
            self.set_language(hollow_bond.settings.get("language"), null, Object.keys(sentences)[0] || language);
        };

        this.add = (new_sentences, overwrite = true) => {
            for(const language in new_sentences){
                if(!sentences[language])
                    sentences[language] = {};
                for(const key in new_sentences[language])
                    if(overwrite || sentences[language][key] === undefined)
                        sentences[language][key] = new_sentences[language][key];
            };
        };

        const get_sentence = (strings, _default) => {

            const done = [];

            if(typeof strings == "string")
                strings = [strings];

            for(const current of [language].concat(Object.keys(sentences)))
                if(!done.includes(current)){
                    done.push(current);
                    for(const key of strings)
                        if(sentences[current][key] !== undefined)
                            return sentences[current][key];
                };
            if(_default !== null)
                return _default;
            for(const text of strings)
                return text;
            return "";
        };

        this.get = (strings, inputs = null, _default = null) => Common.string_variables(get_sentence(strings, _default), inputs);

        this.update = () => {
            document.querySelectorAll("[data-i18n]").forEach(element => {

                const text = self.get(element.getAttribute("data-i18n"));

                if(element.getAttribute("data-i18n-without") != "true")
                    element.textContent = text;
                ["title", "alt", "placeholder"].forEach(key => {
                    element.hasAttribute(key) && element.setAttribute(key, text);
                });

            })
        };

        this.set_language = new_language => {
            if(language != new_language && sentences[new_language]){
                language = new_language;
                self.update();
            };
        };

        this.get_languages = () => Object.keys(sentences);
        
        this.get_current_language = () => language;

        constructor();

    };

    return I18NManager;
})();

Por otro lado, tenemos una librería con métodos comunes la cual es /Public/ecma/Utils/Common.ecma.js:

"use strict";

const Common = (function(){

    const Common = function(){};

    Common.get_keys = (...items) => items.reduce((keys, item) => {

        if(item){
            if(typeof item == "string" && /^[a-z_][a-z0-9_]*$/i.test(item) && !keys.includes(item))
                keys.push(item);
            else if(item instanceof Array)
                for(const key of Common.get_keys(...item))
                    keys.includes(item) || keys.push(key);
        };

        return keys;
    }, []);

    Common.get_dictionaries = (...items) => items.reduce((dictionaries, item) => {

        if(item){
            if(item.constructor == Object)
                dictionaries.push(item);
            else if(item instanceof Array)
                dictionaries.push(...Common.get_dictionaries(...item));
        };

        return dictionaries;
    }, []);

    Common.get_value = (keys, inputs, _default = null) => {
        if((keys = Common.get_keys(keys)).length)
            for(const subinputs of Common.get_dictionaries(inputs))
                for(const key of keys)
                    if(subinputs[key] !== undefined)
                        return subinputs[key];
        return _default;
    };

    Common.get_dictionary = (items, overwrite = false) => {

        if(items){
            if(items.constructor == Object)
                return items;
            else if(items instanceof Array)
                return items.reduce((dictionary, item) => {
                    Object.entries(Common.get_dictionary(item)).forEach(([key, value]) => {
                        if(overwrite || dictionary[key] === undefined)
                            dictionary[key] = value;
                    });
                    return dictionary;
                }, {});
        };

        return {};
    };

    Common.string_variables = (string, variables, _default = null) => {

        variables = Common.get_dictionary(variables);

        return ("" + string).replace(/\{([^\{\}]+)\}/g, (all, key) => (
            variables[key] !== undefined ? variables[key] : 
            _default !== null ? _default : 
        all));
    };

    Common.execute_array = (array, each_callback, end_callback, i = 0) => {
        if(i == array.length)
            end_callback();
        else 
            each_callback(array[i], () => {
                Common.execute_array(array, each_callback, end_callback, i + 1);
            });
    };

    Common.SCSS = (data, selector = "") => {
        
        /** @type {Arrya.<string>} */
        const childs = [];
        /** @type {string} */
        let css = ``;

        Object.entries(data).forEach(([key, value]) => {
            if(value && value.constructor == Object)
                key.split(",").forEach(key => {

                    /** @type {string} */
                    const subselector = `${(selector ? selector + (
                              key[0] == "&" ? "" : " "
                          ) : "") + (
                              key[0] == "&" ? key.substring(1) : key
                          )}`, 
                          /** @type {Array<string>} */
                          subcss = Common.SCSS(value, subselector);

                    childs.push(`\n\n${subselector}{${subcss[0]}\n}`, ...subcss.slice(1));

                });
            else 
                css += `\n    ${key.replace(/[^a-z0-9]+/gi, "-")} : ${value === null ? "none" : value};`;
        });

        if(!selector){

            /** @type {HTMLStyleElement} */
            let style = document.querySelector("head>style");
            
            if(!style){
                style = document.querySelector("head").appendChild(document.createElement("style"));
                [
                    ["data-type", "text/css;charset=utf-8"], 
                    ["data-language", "CSS3"],
                    ["charset", "utf-8"]
                ].forEach(([key, value]) => style.setAttribute(key, value === null ? "none" : value));
            };

            style.appendChild(document.createTextNode(css + childs.join("")));

        };

        return [css, ...childs];
    };

    return Common;
})();

Otra librería la cual es un diccionario para almacenar la configuración la cual es /Public/ecma/Utils/Settings.ecma.js:

"use strict";

/** @type {Dictionary.<string, any|null>} */
const settings = {
    cells : 40, 
    position : "body", 
    class_name : "hollow-bond", 
    frames_per_second : 12, 
    language : "espanol", 

    ...(typeof custom_settings == "undefined" ? {} : custom_settings)
};

Otra librería la cual es un diccionario de diccionarios con los textos donde el primer nivel, la clave indica el idioma, y en el diccionario anidado, la clave identifica al texto internacionalizado /Public/ecma/Utils/I18N.ecma.js:

"use strict";

/** @type {Dictionary.<string, Dictionary.<string, string>>} */
const i18n = {
    espanol : {}
};

Por otro lado, tenemos la librería de estilos, que es un JS que simula una especie de SASS la cual es /Public/ecma/Utils/Styles.ecma.js:

"use strict";

/** @type {Dictionary.<string, any|null>} */
const SCSS = {
    ...{
        "body,html" : {
            height : "100%", 
            margin : "0em"
        }
    }, 
    ...{
        ".hollow-bond" : {
            position : "relative", 
            top : "0em", 
            left : "0em", 
            width : "100%", 
            height : "100%"
        }
    }, 
    ...(typeof custom_styles == "undefined" ? {} : custom_styles)
};

Por otro lado, tenemos un directorio que está excluído del proyecto mediante /.gitignore que es /Public/data donde metemos cosas personalizadas o que no queramos incluir en el mismo, como pueden ser bases de datos JS, Assets agenos, etc.

Finalmente tenemos el Index del proyecto el cual es /Public/index.html:

<!DOCTYPE html>
<html lang="es">
    <head>
        <title data-i18n="hollow_bond">Hollow Bond</title>
        <meta http-equiv="content-type" content="text/html;charset=utf-8" />
        <meta charset="utf-8" />
        <meta name="viewport" content="width=device-width,initial-scale=1.0" />

        <link rel="icon" type="image/svg+xml" href="./images/logo.svg">

        <meta name="xdoc:link" content="https://hollowbond.k3y.pw/" />
        <meta name="xdoc:git" content="https://git.k3y.pw/KyMAN/HollowBond" />
        <meta name="xdoc:authors" content="KyMAN" />
        <meta name="xdoc:ai-authors" content="Gemini" />
        <meta name="xdoc:since" content="20260922" />
        <meta name="xdoc:version" content="20260922" />
        <meta name="xdoc:license" content="	Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International" />

        <script data-type="text/javascript;charset=utf-8" data-language="ECMAScript 2015" src="./data/database.ecma.js" charset="utf-8" data-crossorigin="anonymous"></script>
        <script data-type="text/javascript;charset=utf-8" data-language="ECMAScript 2015" src="./data/i18n.ecma.js" charset="utf-8" data-crossorigin="anonymous"></script>
        <script data-type="text/javascript;charset=utf-8" data-language="ECMAScript 2015" src="./data/settings.ecma.js" charset="utf-8" data-crossorigin="anonymous"></script>
        <script data-type="text/javascript;charset=utf-8" data-language="ECMAScript 2015" src="./data/styles.ecma.js" charset="utf-8" data-crossorigin="anonymous"></script>

        <script data-type="text/javascript;charset=utf-8" data-language="ECMAScript 2015" src="./ecma/Utils/Styles.ecma.js" charset="utf-8" data-crossorigin="anonymous"></script>
        <script data-type="text/javascript;charset=utf-8" data-language="ECMAScript 2015" src="./ecma/Utils/Common.ecma.js" charset="utf-8" data-crossorigin="anonymous"></script>
        <script data-type="text/javascript;charset=utf-8" data-language="ECMAScript 2015" src="./ecma/Utils/I18N.ecma.js" charset="utf-8" data-crossorigin="anonymous"></script>
        <script data-type="text/javascript;charset=utf-8" data-language="ECMAScript 2015" src="./ecma/Utils/Settings.ecma.js" charset="utf-8" data-crossorigin="anonymous"></script>
        
        <script data-type="text/javascript;charset=utf-8" data-language="ECMAScript 2015" src="./ecma/Managers/SettingsManager.ecma.js" charset="utf-8" data-crossorigin="anonymous"></script>
        <script data-type="text/javascript;charset=utf-8" data-language="ECMAScript 2015" src="./ecma/Managers/I18NManager.ecma.js" charset="utf-8" data-crossorigin="anonymous"></script>

        <script data-type="text/javascript;charset=utf-8" data-language="ECMAScript 2015" src="./ecma/Application/HollowBond.ecma.js" charset="utf-8" data-crossorigin="anonymous"></script>

        <script data-type="text/javascript;charset=utf-8" data-language="ECMAScript 2015" charset="utf-8">
            "use strict";

            const hollow_bond = new HollowBond();

        </script>

    </head>
    <body></body>
</html>