commit
bb236ff1be
5
.gitignore
vendored
5
.gitignore
vendored
@ -19,4 +19,7 @@ var/run/*
|
||||
.env
|
||||
venv/
|
||||
node_modules
|
||||
tmp.py
|
||||
tmp.py
|
||||
!/plugins/user/Dashboard/*
|
||||
data/www/plugins/*
|
||||
!data/www/plugins/.gitkeep
|
||||
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
0
data/www/plugins/.gitkeep
Executable file
@ -115,9 +115,24 @@ main {
|
||||
flex-direction: column;
|
||||
justify-content: flex-start;
|
||||
align-items: flex-start;
|
||||
padding: 0 10px 40px 10px;
|
||||
padding: 10px 10px 40px 10px;
|
||||
background: $layoutBackground;
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -1,4 +1,3 @@
|
||||
|
||||
class WebDirConstant:
|
||||
|
||||
FOLDER_TEMPLATES = "views"
|
||||
@ -6,6 +5,6 @@ class WebDirConstant:
|
||||
FOLDER_STATIC_WEB_UPLOADS = "uploads"
|
||||
FOLDER_STATIC_WEB_ASSETS = "www"
|
||||
FOLDER_PLUGIN_HOOK = "hook"
|
||||
FOLDER_PLUGIN_STATIC_SRC = "static"
|
||||
FOLDER_PLUGIN_STATIC_DST = "plugins"
|
||||
FOLDER_CONTROLLER = "controller"
|
||||
|
||||
|
||||
|
||||
@ -11,7 +11,7 @@ class CoreController(ObController):
|
||||
self._app.add_url_rule('/favicon.ico', 'favicon', self.favicon, methods=['GET'])
|
||||
|
||||
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()
|
||||
|
||||
rendered_content = render_template_string(template_content)
|
||||
@ -19,4 +19,4 @@ class CoreController(ObController):
|
||||
return self._app.response_class(rendered_content, mimetype='application/json')
|
||||
|
||||
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')
|
||||
@ -28,11 +28,11 @@ class ObController(abc.ABC):
|
||||
|
||||
return self._plugin
|
||||
|
||||
def get_template_folder(self):
|
||||
return self._web_server.get_template_folder()
|
||||
def get_template_dir(self):
|
||||
return self._web_server.get_template_dir()
|
||||
|
||||
def get_web_folder(self):
|
||||
return self._web_server.get_web_folder()
|
||||
def get_web_dir(self):
|
||||
return self._web_server.get_web_dir()
|
||||
|
||||
def reload_web_server(self):
|
||||
self._web_server.reload()
|
||||
@ -45,3 +45,6 @@ class ObController(abc.ABC):
|
||||
|
||||
def get_external_storage_server(self):
|
||||
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)
|
||||
|
||||
@ -13,8 +13,6 @@ from src.model.hook.FunctionalHookRegistration import FunctionalHookRegistration
|
||||
from src.service.ModelStore import ModelStore
|
||||
from src.service.TemplateRenderer import TemplateRenderer
|
||||
from src.constant.WebDirConstant import WebDirConstant
|
||||
from src.service.AliasFileSystemLoader import AliasFileSystemLoader
|
||||
|
||||
|
||||
|
||||
class ObPlugin(abc.ABC):
|
||||
@ -26,7 +24,7 @@ class ObPlugin(abc.ABC):
|
||||
self._plugin_dir = plugin_dir
|
||||
self._model_store = model_store
|
||||
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
|
||||
def use_id(self) -> str:
|
||||
@ -60,6 +58,9 @@ class ObPlugin(abc.ABC):
|
||||
def get_plugin_variable_name(self, name: str) -> str:
|
||||
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:
|
||||
return self._model_store.variable().set_variable(
|
||||
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:
|
||||
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]:
|
||||
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
|
||||
|
||||
def render_view(self, template_file: str, **parameters: dict) -> str:
|
||||
return self._template_renderer.render_view(template_file, self, **parameters)
|
||||
|
||||
@ -38,4 +38,6 @@ class HookType(Enum):
|
||||
H_ROOT_JAVASCRIPT = 'h_root_javascript'
|
||||
H_ROOT_NAV_ELEMENT_START = 'h_root_nav_element_start'
|
||||
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'
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import os
|
||||
import shutil
|
||||
import logging
|
||||
import inspect
|
||||
import importlib
|
||||
@ -14,6 +15,7 @@ from src.model.enum.VariableType import VariableType
|
||||
from src.model.enum.HookType import HookType
|
||||
from src.model.hook.HookRegistration import HookRegistration
|
||||
from src.model.hook.StaticHookRegistration import StaticHookRegistration
|
||||
from src.util.UtilFile import copy_files
|
||||
from typing import List, Dict
|
||||
|
||||
|
||||
@ -164,6 +166,12 @@ class PluginStore:
|
||||
# WEB CONTROLLERS
|
||||
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:
|
||||
for variable_name, variable in self._dead_variables_candidates.items():
|
||||
logging.debug("Removing dead plugin variable {}".format(variable_name))
|
||||
|
||||
@ -1,8 +1,9 @@
|
||||
import os
|
||||
import json
|
||||
|
||||
from flask import Flask, send_from_directory, Markup, url_for
|
||||
from typing import List
|
||||
from flask import Flask, send_from_directory, Markup, url_for, request
|
||||
from flask_login import current_user
|
||||
from typing import List, Optional
|
||||
from jinja2 import Environment, FileSystemLoader, select_autoescape
|
||||
from src.service.ModelStore import ModelStore
|
||||
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.FunctionalHookRegistration import FunctionalHookRegistration
|
||||
from src.constant.WebDirConstant import WebDirConstant
|
||||
from src.service.AliasFileSystemLoader import AliasFileSystemLoader
|
||||
from src.util.utils import get_safe_cron_descriptor, \
|
||||
is_cron_in_datetime_moment, \
|
||||
is_cron_in_week_moment, \
|
||||
@ -23,6 +25,7 @@ class TemplateRenderer:
|
||||
self._kernel = kernel
|
||||
self._model_store = model_store
|
||||
self._render_hook = render_hook
|
||||
self._rendering_env = self.init_rendering_env()
|
||||
|
||||
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))
|
||||
@ -80,3 +83,44 @@ class TemplateRenderer:
|
||||
content.append(hook_registration.function())
|
||||
|
||||
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(),
|
||||
)
|
||||
|
||||
@ -53,20 +53,23 @@ class WebServer:
|
||||
def get_app(self):
|
||||
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)
|
||||
|
||||
def get_static_folder(self) -> str:
|
||||
def get_static_dir(self) -> str:
|
||||
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)
|
||||
|
||||
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:
|
||||
self._app = Flask(
|
||||
__name__,
|
||||
template_folder=self.get_template_folder(),
|
||||
static_folder=self.get_static_folder(),
|
||||
template_folder=self.get_template_dir(),
|
||||
static_folder=self.get_static_dir(),
|
||||
)
|
||||
|
||||
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:
|
||||
@self._app.errorhandler(404)
|
||||
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
|
||||
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import os
|
||||
import uuid
|
||||
import math
|
||||
import shutil
|
||||
|
||||
|
||||
def randomize_filename(old_filename: str) -> str:
|
||||
@ -17,3 +18,20 @@ def convert_size(size_bytes):
|
||||
p = math.pow(1024, i)
|
||||
s = round(size_bytes / p, 2)
|
||||
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)
|
||||
|
||||
@ -1 +1 @@
|
||||
2.1.1
|
||||
2.2.0
|
||||
|
||||
@ -168,25 +168,14 @@
|
||||
|
||||
{% if authenticated_view %}
|
||||
<div class="context-bar {{ 'hidden' if not show_context_bar }}">
|
||||
{% if current_dynmenu %}
|
||||
<div class="context-menu">
|
||||
<div class="inner">
|
||||
<ul class="pills">
|
||||
{% for menu in current_dynmenu.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 %}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% block pill_menu %}
|
||||
{% if current_dynmenu %}
|
||||
{% with pills=current_dynmenu.pills %}
|
||||
{% include 'core/pill-menu.jinja.html' %}
|
||||
{% endwith %}
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
||||
|
||||
<div class="context-divider"></div>
|
||||
|
||||
19
views/core/pill-menu.jinja.html
Normal file
19
views/core/pill-menu.jinja.html
Normal 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>
|
||||
@ -36,7 +36,7 @@
|
||||
<div class="top-actions">
|
||||
{{ 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>
|
||||
{{ l.common_new_folder }}
|
||||
</button>
|
||||
|
||||
Loading…
Reference in New Issue
Block a user