bump 2.2.0

This commit is contained in:
jr-k 2024-07-30 18:13:00 +02:00
parent 20dcb3659a
commit 143f6b5b44
19 changed files with 153 additions and 71 deletions

3
.gitignore vendored
View File

@ -19,4 +19,5 @@ var/run/*
.env .env
venv/ venv/
node_modules node_modules
tmp.py tmp.py
!/plugins/user/Dashboard/*

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

0
data/www/plugins/.gitkeep Executable file
View File

View File

@ -0,0 +1,3 @@
.inner h3 {
color: red !important;
}

View File

@ -115,9 +115,24 @@ main {
flex-direction: column; flex-direction: column;
justify-content: flex-start; justify-content: flex-start;
align-items: flex-start; align-items: flex-start;
padding: 0 10px 40px 10px; padding: 10px 10px 40px 10px;
background: $layoutBackground; background: $layoutBackground;
align-self: stretch; align-self: stretch;
h1, h2, h3, h4, h5, h6 {
color: $gscaleD;
}
p {
font-size: 12px;
line-height: 18px;
display: flex;
margin-bottom: 5px;
flex-direction: row;
justify-content: flex-start;
align-items: center;
color: $gscale6;
}
} }
} }

View File

@ -1,4 +1,3 @@
class WebDirConstant: class WebDirConstant:
FOLDER_TEMPLATES = "views" FOLDER_TEMPLATES = "views"
@ -6,6 +5,6 @@ class WebDirConstant:
FOLDER_STATIC_WEB_UPLOADS = "uploads" FOLDER_STATIC_WEB_UPLOADS = "uploads"
FOLDER_STATIC_WEB_ASSETS = "www" FOLDER_STATIC_WEB_ASSETS = "www"
FOLDER_PLUGIN_HOOK = "hook" FOLDER_PLUGIN_HOOK = "hook"
FOLDER_PLUGIN_STATIC_SRC = "static"
FOLDER_PLUGIN_STATIC_DST = "plugins"
FOLDER_CONTROLLER = "controller" FOLDER_CONTROLLER = "controller"

View File

@ -11,7 +11,7 @@ class CoreController(ObController):
self._app.add_url_rule('/favicon.ico', 'favicon', self.favicon, methods=['GET']) self._app.add_url_rule('/favicon.ico', 'favicon', self.favicon, methods=['GET'])
def manifest(self): def manifest(self):
with open("{}/manifest.jinja.json".format(self.get_template_folder()), 'r') as file: with open("{}/manifest.jinja.json".format(self.get_template_dir()), 'r') as file:
template_content = file.read() template_content = file.read()
rendered_content = render_template_string(template_content) rendered_content = render_template_string(template_content)
@ -19,4 +19,4 @@ class CoreController(ObController):
return self._app.response_class(rendered_content, mimetype='application/json') return self._app.response_class(rendered_content, mimetype='application/json')
def favicon(self): def favicon(self):
return send_file("{}/favicon.ico".format(self.get_web_folder()), mimetype='image/x-icon') return send_file("{}/favicon.ico".format(self.get_web_dir()), mimetype='image/x-icon')

View File

@ -28,11 +28,11 @@ class ObController(abc.ABC):
return self._plugin return self._plugin
def get_template_folder(self): def get_template_dir(self):
return self._web_server.get_template_folder() return self._web_server.get_template_dir()
def get_web_folder(self): def get_web_dir(self):
return self._web_server.get_web_folder() return self._web_server.get_web_dir()
def reload_web_server(self): def reload_web_server(self):
self._web_server.reload() self._web_server.reload()
@ -45,3 +45,6 @@ class ObController(abc.ABC):
def get_external_storage_server(self): def get_external_storage_server(self):
return self._kernel.external_storage_server return self._kernel.external_storage_server
def render_view(self, template_file: str, **parameters: dict) -> str:
return self._template_renderer.render_view(template_file, self.plugin(), **parameters)

View File

@ -13,8 +13,6 @@ from src.model.hook.FunctionalHookRegistration import FunctionalHookRegistration
from src.service.ModelStore import ModelStore from src.service.ModelStore import ModelStore
from src.service.TemplateRenderer import TemplateRenderer from src.service.TemplateRenderer import TemplateRenderer
from src.constant.WebDirConstant import WebDirConstant from src.constant.WebDirConstant import WebDirConstant
from src.service.AliasFileSystemLoader import AliasFileSystemLoader
class ObPlugin(abc.ABC): class ObPlugin(abc.ABC):
@ -26,7 +24,7 @@ class ObPlugin(abc.ABC):
self._plugin_dir = plugin_dir self._plugin_dir = plugin_dir
self._model_store = model_store self._model_store = model_store
self._template_renderer = template_renderer self._template_renderer = template_renderer
self._rendering_env = self._init_rendering_env() self._rendering_env = template_renderer.init_rendering_env(self._plugin_dir)
@abc.abstractmethod @abc.abstractmethod
def use_id(self) -> str: def use_id(self) -> str:
@ -60,6 +58,9 @@ class ObPlugin(abc.ABC):
def get_plugin_variable_name(self, name: str) -> str: def get_plugin_variable_name(self, name: str) -> str:
return "{}_{}".format(self.get_plugin_variable_prefix(), name) return "{}_{}".format(self.get_plugin_variable_prefix(), name)
def get_plugin_static_src_dir(self) -> str:
return "{}/{}".format(self._plugin_dir, WebDirConstant.FOLDER_PLUGIN_STATIC_SRC)
def add_variable(self, name: str, value='', section: str = '', type: VariableType = VariableType.STRING, editable: bool = True, description: str = '', description_edition: str = '', selectables: Optional[Dict[str, str]] = None, unit: Optional[VariableUnit] = None, refresh_player: bool = False) -> Variable: def add_variable(self, name: str, value='', section: str = '', type: VariableType = VariableType.STRING, editable: bool = True, description: str = '', description_edition: str = '', selectables: Optional[Dict[str, str]] = None, unit: Optional[VariableUnit] = None, refresh_player: bool = False) -> Variable:
return self._model_store.variable().set_variable( return self._model_store.variable().set_variable(
name=self.get_plugin_variable_name(name), name=self.get_plugin_variable_name(name),
@ -81,32 +82,9 @@ class ObPlugin(abc.ABC):
def add_functional_hook_registration(self, hook: HookType, priority: int = 0, function=None) -> FunctionalHookRegistration: def add_functional_hook_registration(self, hook: HookType, priority: int = 0, function=None) -> FunctionalHookRegistration:
return FunctionalHookRegistration(plugin=self, hook=hook, priority=priority, function=function) return FunctionalHookRegistration(plugin=self, hook=hook, priority=priority, function=function)
def _init_rendering_env(self) -> Environment:
alias_paths = {
"::": "{}/".format(WebDirConstant.FOLDER_TEMPLATES),
"@": "{}/{}/".format(self._plugin_dir.replace(self._kernel.get_application_dir(), ''), WebDirConstant.FOLDER_TEMPLATES)
}
env = Environment(
loader=AliasFileSystemLoader(
searchpath=self._kernel.get_application_dir(),
alias_paths=alias_paths
),
autoescape=select_autoescape(['html', 'xml'])
)
return env
def render_view(self, template_file: str, **parameters: dict) -> str:
template = self.get_rendering_env().get_template(template_file)
return template.render(
request=request,
url_for=url_for,
**parameters,
**self._template_renderer.get_view_globals(),
)
def translate(self, token, resolve=False) -> Union[Dict, str]: def translate(self, token, resolve=False) -> Union[Dict, str]:
token = token if token.startswith(self.use_id()) else "{}_{}".format(self.use_id(), token) token = token if token.startswith(self.use_id()) else "{}_{}".format(self.use_id(), token)
return self._model_store.lang().translate(token) if resolve else token return self._model_store.lang().translate(token) if resolve else token
def render_view(self, template_file: str, **parameters: dict) -> str:
return self._template_renderer.render_view(template_file, self, **parameters)

View File

@ -38,4 +38,6 @@ class HookType(Enum):
H_ROOT_JAVASCRIPT = 'h_root_javascript' H_ROOT_JAVASCRIPT = 'h_root_javascript'
H_ROOT_NAV_ELEMENT_START = 'h_root_nav_element_start' H_ROOT_NAV_ELEMENT_START = 'h_root_nav_element_start'
H_ROOT_NAV_ELEMENT_END = 'h_root_nav_element_end' H_ROOT_NAV_ELEMENT_END = 'h_root_nav_element_end'
H_ROOT_PILL_ELEMENT_START = 'h_root_pill_element_start'
H_ROOT_PILL_ELEMENT_END = 'h_root_pill_element_end'
H_ROOT_FOOTER = 'h_root_footer' H_ROOT_FOOTER = 'h_root_footer'

View File

@ -1,4 +1,5 @@
import os import os
import shutil
import logging import logging
import inspect import inspect
import importlib import importlib
@ -14,6 +15,7 @@ from src.model.enum.VariableType import VariableType
from src.model.enum.HookType import HookType from src.model.enum.HookType import HookType
from src.model.hook.HookRegistration import HookRegistration from src.model.hook.HookRegistration import HookRegistration
from src.model.hook.StaticHookRegistration import StaticHookRegistration from src.model.hook.StaticHookRegistration import StaticHookRegistration
from src.util.UtilFile import copy_files
from typing import List, Dict from typing import List, Dict
@ -164,6 +166,12 @@ class PluginStore:
# WEB CONTROLLERS # WEB CONTROLLERS
self.load_controllers(plugin) self.load_controllers(plugin)
# STATIC FILES
static_src = plugin.get_plugin_static_src_dir()
static_dst = self._web_server.get_plugin_static_dst_dir(plugin.use_id())
if os.path.exists(static_src):
copy_files(static_src, static_dst)
def clean_dead_variables(self) -> None: def clean_dead_variables(self) -> None:
for variable_name, variable in self._dead_variables_candidates.items(): for variable_name, variable in self._dead_variables_candidates.items():
logging.debug("Removing dead plugin variable {}".format(variable_name)) logging.debug("Removing dead plugin variable {}".format(variable_name))

View File

@ -1,8 +1,9 @@
import os import os
import json import json
from flask import Flask, send_from_directory, Markup, url_for from flask import Flask, send_from_directory, Markup, url_for, request
from typing import List from flask_login import current_user
from typing import List, Optional
from jinja2 import Environment, FileSystemLoader, select_autoescape from jinja2 import Environment, FileSystemLoader, select_autoescape
from src.service.ModelStore import ModelStore from src.service.ModelStore import ModelStore
from src.model.enum.HookType import HookType from src.model.enum.HookType import HookType
@ -10,6 +11,7 @@ from src.model.hook.HookRegistration import HookRegistration
from src.model.hook.StaticHookRegistration import StaticHookRegistration from src.model.hook.StaticHookRegistration import StaticHookRegistration
from src.model.hook.FunctionalHookRegistration import FunctionalHookRegistration from src.model.hook.FunctionalHookRegistration import FunctionalHookRegistration
from src.constant.WebDirConstant import WebDirConstant from src.constant.WebDirConstant import WebDirConstant
from src.service.AliasFileSystemLoader import AliasFileSystemLoader
from src.util.utils import get_safe_cron_descriptor, \ from src.util.utils import get_safe_cron_descriptor, \
is_cron_in_datetime_moment, \ is_cron_in_datetime_moment, \
is_cron_in_week_moment, \ is_cron_in_week_moment, \
@ -23,6 +25,7 @@ class TemplateRenderer:
self._kernel = kernel self._kernel = kernel
self._model_store = model_store self._model_store = model_store
self._render_hook = render_hook self._render_hook = render_hook
self._rendering_env = self.init_rendering_env()
def cron_descriptor(self, expression: str, use_24hour_time_format=True) -> str: def cron_descriptor(self, expression: str, use_24hour_time_format=True) -> str:
return get_safe_cron_descriptor(expression, use_24hour_time_format, self._model_store.lang().get_lang(local_with_country=True)) return get_safe_cron_descriptor(expression, use_24hour_time_format, self._model_store.lang().get_lang(local_with_country=True))
@ -80,3 +83,44 @@ class TemplateRenderer:
content.append(hook_registration.function()) content.append(hook_registration.function())
return Markup("".join(content)) return Markup("".join(content))
def init_rendering_env(self, base_folder: Optional[str] = None) -> Environment:
base_folder = "{}/".format(base_folder.replace(self._kernel.get_application_dir(), '')) if base_folder else ''
alias_paths = {
"::": "{}/".format(WebDirConstant.FOLDER_TEMPLATES),
"@": "{}{}/".format(base_folder, WebDirConstant.FOLDER_TEMPLATES)
}
env = Environment(
loader=AliasFileSystemLoader(
searchpath=self._kernel.get_application_dir(),
alias_paths=alias_paths
),
autoescape=select_autoescape(['html', 'xml'])
)
return env
def get_rendering_env(self) -> Environment:
return self._rendering_env
def render_view(self, template_file: str, plugin: Optional = None, **parameters: dict) -> str:
base_rendering_env = plugin.get_rendering_env() if plugin else self.get_rendering_env()
template = base_rendering_env.get_template(template_file)
if plugin:
parameters['STATIC_PLUGIN_PREFIX'] = "/{}/{}/{}/{}/".format(
WebDirConstant.FOLDER_STATIC,
WebDirConstant.FOLDER_STATIC_WEB_ASSETS,
WebDirConstant.FOLDER_PLUGIN_STATIC_DST,
plugin.use_id()
)
return template.render(
request=request,
url_for=url_for,
current_user=current_user,
**parameters,
**self.get_view_globals(),
)

View File

@ -53,20 +53,23 @@ class WebServer:
def get_app(self): def get_app(self):
return self._app return self._app
def get_template_folder(self) -> str: def get_template_dir(self) -> str:
return "{}/{}".format(self._kernel.get_application_dir(), WebDirConstant.FOLDER_TEMPLATES) return "{}/{}".format(self._kernel.get_application_dir(), WebDirConstant.FOLDER_TEMPLATES)
def get_static_folder(self) -> str: def get_static_dir(self) -> str:
return "{}/{}".format(self._kernel.get_application_dir(), WebDirConstant.FOLDER_STATIC) return "{}/{}".format(self._kernel.get_application_dir(), WebDirConstant.FOLDER_STATIC)
def get_web_folder(self) -> str: def get_web_dir(self) -> str:
return "{}/{}/{}".format(self._kernel.get_application_dir(), WebDirConstant.FOLDER_STATIC, WebDirConstant.FOLDER_STATIC_WEB_ASSETS) return "{}/{}/{}".format(self._kernel.get_application_dir(), WebDirConstant.FOLDER_STATIC, WebDirConstant.FOLDER_STATIC_WEB_ASSETS)
def get_plugin_static_dst_dir(self, plugin_id: str) -> str:
return "{}/{}/{}".format(self.get_web_dir(), WebDirConstant.FOLDER_PLUGIN_STATIC_DST, plugin_id)
def _setup_flask_app(self) -> None: def _setup_flask_app(self) -> None:
self._app = Flask( self._app = Flask(
__name__, __name__,
template_folder=self.get_template_folder(), template_folder=self.get_template_dir(),
static_folder=self.get_static_folder(), static_folder=self.get_static_dir(),
) )
self._app.config['UPLOAD_FOLDER'] = "{}/{}".format(WebDirConstant.FOLDER_STATIC, WebDirConstant.FOLDER_STATIC_WEB_UPLOADS) self._app.config['UPLOAD_FOLDER'] = "{}/{}".format(WebDirConstant.FOLDER_STATIC, WebDirConstant.FOLDER_STATIC_WEB_UPLOADS)
@ -118,5 +121,5 @@ class WebServer:
def _setup_web_errors(self) -> None: def _setup_web_errors(self) -> None:
@self._app.errorhandler(404) @self._app.errorhandler(404)
def not_found(e): def not_found(e):
return send_from_directory(self.get_template_folder(), 'core/error404.html'), 404 return send_from_directory(self.get_template_dir(), 'core/error404.html'), 404

View File

@ -1,6 +1,7 @@
import os import os
import uuid import uuid
import math import math
import shutil
def randomize_filename(old_filename: str) -> str: def randomize_filename(old_filename: str) -> str:
@ -17,3 +18,20 @@ def convert_size(size_bytes):
p = math.pow(1024, i) p = math.pow(1024, i)
s = round(size_bytes / p, 2) s = round(size_bytes / p, 2)
return f"{s} {size_name[i]}" return f"{s} {size_name[i]}"
def copy_files(src, dst):
if not os.path.exists(dst):
os.makedirs(dst)
for root, dirs, files in os.walk(src):
# Construct the destination path
dst_path = os.path.join(dst, os.path.relpath(root, src))
if not os.path.exists(dst_path):
os.makedirs(dst_path)
for file in files:
# Copy each file to the destination
src_file = os.path.join(root, file)
dst_file = os.path.join(dst_path, file)
shutil.copy2(src_file, dst_file)

View File

@ -1 +1 @@
2.1.1 2.2.0

View File

@ -168,25 +168,14 @@
{% if authenticated_view %} {% if authenticated_view %}
<div class="context-bar {{ 'hidden' if not show_context_bar }}"> <div class="context-bar {{ 'hidden' if not show_context_bar }}">
{% if current_dynmenu %}
<div class="context-menu"> {% block pill_menu %}
<div class="inner"> {% if current_dynmenu %}
<ul class="pills"> {% with pills=current_dynmenu.pills %}
{% for menu in current_dynmenu.pills %} {% include 'core/pill-menu.jinja.html' %}
<li class="{{ 'active' if active_route == menu.route or active_route == menu.route_alt }}"> {% endwith %}
{% set href = menu.url_for if menu.url_for else url_for(menu.route) %} {% endif %}
<a href="{{ href }}"> {% endblock %}
<span class="icon">
<i class="fa {{ menu.icon }}"></i>
</span>
{{ menu.name }}
</a>
</li>
{% endfor %}
</ul>
</div>
</div>
{% endif %}
<div class="context-divider"></div> <div class="context-divider"></div>

View File

@ -0,0 +1,19 @@
<div class="context-menu">
<div class="inner">
<ul class="pills">
{{ HOOK(H_ROOT_PILL_ELEMENT_START) }}
{% for menu in pills %}
<li class="{{ 'active' if active_route == menu.route or active_route == menu.route_alt }}">
{% set href = menu.url_for if menu.url_for else url_for(menu.route) %}
<a href="{{ href }}">
<span class="icon">
<i class="fa {{ menu.icon }}"></i>
</span>
{{ menu.name }}
</a>
</li>
{% endfor %}
{{ HOOK(H_ROOT_PILL_ELEMENT_END) }}
</ul>
</div>
</div>

View File

@ -36,7 +36,7 @@
<div class="top-actions"> <div class="top-actions">
{{ HOOK(H_SLIDESHOW_CONTENT_TOOLBAR_ACTIONS_START) }} {{ HOOK(H_SLIDESHOW_CONTENT_TOOLBAR_ACTIONS_START) }}
<button type="button" class="folder-add btn-neutral"> <button type="button" class="btn folder-add btn-neutral">
<i class="fa fa-folder-plus icon-left"></i> <i class="fa fa-folder-plus icon-left"></i>
{{ l.common_new_folder }} {{ l.common_new_folder }}
</button> </button>