VENOM-10: WIP
|
@ -1,7 +1,9 @@
|
|||
<?php
|
||||
|
||||
//register modules -> need to have the Module Class at parent with the init function ;)
|
||||
$modules = [];
|
||||
$modules = [
|
||||
'role' => Modules\RoleModule::class
|
||||
];
|
||||
|
||||
// register controllers that can handle templates ;) need to have a render function for this
|
||||
$controllers = [
|
||||
|
|
0
logs/.gitkeep
Normal file → Executable file
|
@ -1 +1 @@
|
|||
:focus{outline:0}*{box-sizing:border-box}body,html{width:100vw;height:100vh;overflow:hidden;padding:0;margin:0;background:#1f2857;background:linear-gradient(144deg,#1f2954 25%,#000 50%,#5e002c 80%);background-attachment:fixed;color:#fff;font-family:sans-serif;font-size:16px}.hide{display:none!important}v-table{display:block}v-table v-table-footer,v-table v-table-header,v-table v-table-row{display:grid;grid-auto-columns:1fr;grid-auto-flow:column;-moz-column-gap:20px;column-gap:20px}v-table v-table-header h2{text-align:center}v-table v-table-row{margin:10px 0}v-table .grid12{grid-template-columns:repeat(12,[col-start] 1fr);-moz-column-gap:5px;column-gap:5px}v-table .grid12 .col-1to4{grid-column:1/3}v-table .grid12 .col-1to6{grid-column:1/6}v-table .grid12 .col-5to6{grid-column:5/6}v-table .grid12 .col-6to11{grid-column:6/11}v-table .grid12 .col-6to9{grid-column:6/9}v-table .grid12 .col-9to10{grid-column:9/10}v-table .grid12 .col-11to13{grid-column:11/13}v-label,v-option,v-options,v-select{display:inline-block;box-sizing:border-box}v-select{display:block;position:relative;margin-bottom:.5rem}v-label{padding:.5rem 1rem;background-color:#3949ab;color:#fff;cursor:pointer;display:block;border-radius:4px}v-options{position:absolute;display:flex;border-radius:4px;flex-direction:column;overflow:hidden;max-height:0;top:.5rem;left:.5rem;background-color:#3949ab;z-index:1000;cursor:pointer;color:#fff;box-shadow:0 3px 6px rgba(0,0,0,.16),0 3px 6px rgba(0,0,0,.23)}v-option{padding:.5rem 1rem;min-width:200px}v-option:not([disabled]):hover{background-color:rgba(0,0,0,.3)}v-option[selected]{background-color:#3949ab;color:#fff}v-option[disabled]{color:#aaa}main{display:flex;height:100vh;overflow:hidden}.menu{width:220px;background-color:#1b1b1b;box-shadow:0 3px 6px rgba(0,0,0,.16),0 3px 6px rgba(0,0,0,.23);flex-shrink:0;display:flex;flex-direction:column}.menu .logo{text-align:center;font-family:monospace}.menu div[data-link]{padding:.75rem .5rem;position:relative}.menu div[data-link]:after{background-color:#3949ab;content:"";position:absolute;left:0;bottom:0;height:3px;width:100%;transform:scaleX(0);transition:transform .4s;transform-origin:left}.menu div[data-link]:hover:after{transform:scaleX(1)}.menu div[data-link]:last-child{margin-top:auto}.menu div[data-link].active{font-weight:700}.content-area{padding:0 1%;margin:1rem 1.5rem;flex-grow:1;overflow-y:auto;width:100%;max-height:100%;background:rgba(27,27,27,.5)}.content-area .inline{display:inline}.content-area .inline div{display:inline}.content-area .btn-line{margin-top:35px}.content-area .btn-line div{text-align:right}.content-area .btn-line div button{display:inline;margin-left:10px;min-width:100px}.content-area textarea{background:rgba(27,27,27,.5);color:#fff;margin:15px 0 0 0;font-family:sans-serif;font-size:1.1rem;min-width:100%}.content-area .modules div{padding:6px 20px 6px 0}.content-area .icon-cont div{display:inline-block;vertical-align:middle;margin-top:5px}.content-area .icon-cont .icon{width:18px;height:18px;background-size:cover;margin:0 15px 0 0;fill:#fff}.content-area .icon-cont .icon-add{background-image:url(../images/icon/ic_add_circle_outline_24px.svg)}.content-area .icon-cont .icon-edit{background-image:url(../images/icon/ic_edit_24px.svg)}.content-area .icon-cont .icon-visibility{background-image:url(../images/icon/ic_visibility_24px.svg)}.content-area .icon-cont .icon-delete{background-image:url(../images/icon/ic_delete_24px.svg)}.content-area .icon-cont .icon-arrow-back{background-image:url(../images/icon/ic_arrow_back_24px.svg);width:24px;height:24px}.content-area .description{margin-top:25px}.content-area .fix-pad{padding-top:14px}
|
||||
main{display:flex;height:100vh;overflow:hidden}.menu{width:220px;background-color:#1b1b1b;box-shadow:0 3px 6px rgba(0,0,0,.16),0 3px 6px rgba(0,0,0,.23);flex-shrink:0;display:flex;flex-direction:column}.menu .logo{text-align:center;font-family:monospace}.menu div[data-link]{padding:.75rem .5rem;position:relative}.menu div[data-link]:after{background-color:#3949ab;content:"";position:absolute;left:0;bottom:0;height:3px;width:100%;transform:scaleX(0);transition:transform .4s;transform-origin:left}.menu div[data-link]:hover:after{transform:scaleX(1)}.menu div[data-link]:last-child{margin-top:auto}.menu div[data-link].active{font-weight:700}.content-area{padding:0 1%;margin:1rem 1.5rem;flex-grow:1;overflow-y:auto;width:100%;max-height:100%;background:rgba(27,27,27,.5)}.content-area .inline{display:inline}.content-area .inline div{display:inline}.content-area .btn-line{margin-top:35px}.content-area .btn-line div{text-align:right}.content-area .btn-line div button{display:inline;margin-left:10px;min-width:100px}.content-area textarea{background:rgba(27,27,27,.5);color:#fff;margin:15px 0 0 0;font-family:sans-serif;font-size:1.1rem;min-width:100%}.content-area .modules div{padding:6px 20px 6px 0}.content-area .fix-pad{padding-top:14px}
|
1
public/theme/admin/icon-sprite.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?><svg xmlns="http://www.w3.org/2000/svg"><symbol viewBox="0 0 24 24" id="vt-add" xmlns="http://www.w3.org/2000/svg"><path fill="#fff" d="M13 7h-2v4H7v2h4v4h2v-4h4v-2h-4V7zm-1-5C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 18c-4.41 0-8-3.59-8-8s3.59-8 8-8 8 3.59 8 8-3.59 8-8 8z"/></symbol><symbol viewBox="0 0 24 24" id="vt-arrow-back" xmlns="http://www.w3.org/2000/svg"><path fill="#fff" d="M20 11H7.83l5.59-5.59L12 4l-8 8 8 8 1.41-1.41L7.83 13H20v-2z"/></symbol><symbol viewBox="0 0 24 24" id="vt-check" xmlns="http://www.w3.org/2000/svg"><path fill="#fff" d="M19 3H5a2 2 0 00-2 2v14a2 2 0 002 2h14a2 2 0 002-2V5a2 2 0 00-2-2zm-9 14l-5-5 1.41-1.41L10 14.17l7.59-7.59L19 8l-9 9z"/></symbol><symbol viewBox="0 0 24 24" id="vt-delete" xmlns="http://www.w3.org/2000/svg"><path fill="#fff" d="M6 19c0 1.1.9 2 2 2h8c1.1 0 2-.9 2-2V7H6v12zM19 4h-3.5l-1-1h-5l-1 1H5v2h14V4z"/><path d="M0 0h24v24H0z" fill="none"/></symbol><symbol viewBox="0 0 24 24" id="vt-edit" xmlns="http://www.w3.org/2000/svg"><path fill="#fff" d="M3 17.25V21h3.75L17.81 9.94l-3.75-3.75L3 17.25zM20.71 7.04a.996.996 0 000-1.41l-2.34-2.34a.996.996 0 00-1.41 0l-1.83 1.83 3.75 3.75 1.83-1.83z"/></symbol><symbol viewBox="0 0 24 24" id="vt-visibility" xmlns="http://www.w3.org/2000/svg"><path fill="#fff" d="M12 4.5C7 4.5 2.73 7.61 1 12c1.73 4.39 6 7.5 11 7.5s9.27-3.11 11-7.5c-1.73-4.39-6-7.5-11-7.5zM12 17c-2.76 0-5-2.24-5-5s2.24-5 5-5 5 2.24 5 5-2.24 5-5 5zm0-8c-1.66 0-3 1.34-3 3s1.34 3 3 3 3-1.34 3-3-1.34-3-3-3z"/></symbol></svg>
|
After Width: | Height: | Size: 1.5 KiB |
|
@ -1 +0,0 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="#fff" d="M13 7h-2v4H7v2h4v4h2v-4h4v-2h-4V7zm-1-5C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 18c-4.41 0-8-3.59-8-8s3.59-8 8-8 8 3.59 8 8-3.59 8-8 8z"/></svg>
|
Before Width: | Height: | Size: 271 B |
|
@ -1 +0,0 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="#fff" d="M20 11H7.83l5.59-5.59L12 4l-8 8 8 8 1.41-1.41L7.83 13H20v-2z"/></svg>
|
Before Width: | Height: | Size: 173 B |
|
@ -1 +0,0 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="#fff" d="M19 3H5c-1.11 0-2 .9-2 2v14c0 1.1.89 2 2 2h14c1.11 0 2-.9 2-2V5c0-1.1-.89-2-2-2zm-9 14l-5-5 1.41-1.41L10 14.17l7.59-7.59L19 8l-9 9z"/></svg>
|
Before Width: | Height: | Size: 244 B |
|
@ -1,4 +0,0 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
|
||||
<path fill="#fff" d="M6 19c0 1.1.9 2 2 2h8c1.1 0 2-.9 2-2V7H6v12zM19 4h-3.5l-1-1h-5l-1 1H5v2h14V4z"/>
|
||||
<path d="M0 0h24v24H0z" fill="none"/>
|
||||
</svg>
|
Before Width: | Height: | Size: 239 B |
|
@ -1 +0,0 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="#fff" d="M3 17.25V21h3.75L17.81 9.94l-3.75-3.75L3 17.25zM20.71 7.04c.39-.39.39-1.02 0-1.41l-2.34-2.34c-.39-.39-1.02-.39-1.41 0l-1.83 1.83 3.75 3.75 1.83-1.83z"/></svg>
|
Before Width: | Height: | Size: 262 B |
|
@ -1 +0,0 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="#fff" d="M12 4.5C7 4.5 2.73 7.61 1 12c1.73 4.39 6 7.5 11 7.5s9.27-3.11 11-7.5c-1.73-4.39-6-7.5-11-7.5zM12 17c-2.76 0-5-2.24-5-5s2.24-5 5-5 5 2.24 5 5-2.24 5-5 5zm0-8c-1.66 0-3 1.34-3 3s1.34 3 3 3 3-1.34 3-3-1.34-3-3-3z"/></svg>
|
Before Width: | Height: | Size: 322 B |
59
public/theme/admin/js/components.js
Normal file
|
@ -0,0 +1,59 @@
|
|||
class Component {
|
||||
constructor(name) {
|
||||
this.name = name;
|
||||
this.start();
|
||||
}
|
||||
|
||||
handle(data, ds) {
|
||||
}
|
||||
|
||||
init() {
|
||||
}
|
||||
|
||||
getUrl(ds) {
|
||||
return '';
|
||||
}
|
||||
|
||||
start() {
|
||||
if (window.routerIsReady) {
|
||||
router.addComponent(this.name || VUtils.tempId(), this);
|
||||
this.init();
|
||||
} else {
|
||||
window.addEventListener('routerReady', this.start.bind(this));
|
||||
}
|
||||
}
|
||||
}
|
||||
class RolesComponent extends Component {
|
||||
constructor() {
|
||||
super("/roles");
|
||||
this.tpl = "rolesList";
|
||||
this.tpl2 = "roleEdit";
|
||||
this._url = "/admin/api/roles";
|
||||
}
|
||||
|
||||
async handle(data, ds) {
|
||||
let meTpl = ds.id ? this.tpl2 : this.tpl;
|
||||
await tpl.loadTemplate(meTpl);
|
||||
return await tpl.renderOn(meTpl, data.content);
|
||||
}
|
||||
|
||||
getUrl(ds) {
|
||||
let url = this._url;
|
||||
if (ds.id) {
|
||||
url += '/' + ds.id;
|
||||
}
|
||||
return url;
|
||||
}
|
||||
}
|
||||
(() => {
|
||||
// init all Components ;)
|
||||
new RolesComponent();
|
||||
|
||||
if (routerIsReady) {
|
||||
document.body.dispatchEvent(new CustomEvent('triggerRouter'));
|
||||
} else {
|
||||
document.addEventListener('routerReady', e => {
|
||||
document.body.dispatchEvent(new CustomEvent('triggerRouter'));
|
||||
})
|
||||
}
|
||||
})();
|
1
public/theme/admin/js/components.min.js
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
class Component{constructor(t){this.name=t,this.start()}handle(t,e){}init(){}getUrl(t){return""}start(){window.routerIsReady?(router.addComponent(this.name||VUtils.tempId(),this),this.init()):window.addEventListener("routerReady",this.start.bind(this))}}class RolesComponent extends Component{constructor(){super("/roles"),this.tpl="rolesList",this.tpl2="roleEdit",this._url="/admin/api/roles"}async handle(t,e){let n=e.id?this.tpl2:this.tpl;return await tpl.loadTemplate(n),await tpl.renderOn(n,t.content)}getUrl(t){let e=this._url;return t.id&&(e+="/"+t.id),e}}new RolesComponent,routerIsReady?document.body.dispatchEvent(new CustomEvent("triggerRouter")):document.addEventListener("routerReady",t=>{document.body.dispatchEvent(new CustomEvent("triggerRouter"))});
|
|
@ -85,7 +85,9 @@ class VUtils {
|
|||
};
|
||||
Node.prototype.createNew = function (tag, options) {
|
||||
let el = document.createElement(tag);
|
||||
if (options.classes) {
|
||||
el.classList.add(...VUtils.get(options.classes, []));
|
||||
}
|
||||
el.id = VUtils.get(options.id, '');
|
||||
el.innerHTML = VUtils.get(options.content, "");
|
||||
VUtils.mergeKeys(options.dataset, el.dataset);
|
||||
|
@ -123,6 +125,7 @@ class VUtils {
|
|||
};
|
||||
}
|
||||
}
|
||||
|
||||
VUtils.makePublic();
|
||||
|
||||
|
||||
|
@ -199,6 +202,7 @@ class VRipple {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
const rippler = new VRipple();
|
||||
|
||||
|
||||
|
@ -426,40 +430,29 @@ class FormHandler {
|
|||
}
|
||||
|
||||
(function () {
|
||||
class VButton extends HTMLElement {
|
||||
class VInput extends HTMLElement {
|
||||
constructor() {
|
||||
super();
|
||||
let self = this;
|
||||
self.id = self.id || VUtils.tempId();
|
||||
self.label = document.createElement('label');
|
||||
self.input = document.createElement('input');
|
||||
self.errorBox = document.createElement('span');
|
||||
self.errorBox.classList.add('error');
|
||||
self.errorBox.innerHTML = self.dataset.error;
|
||||
self.label.setAttribute('for', self.id);
|
||||
self.input.id = self.id;
|
||||
self.input.type = self.getAttribute('type') || 'text';
|
||||
self.label.innerHTML = self.dataset.label;
|
||||
self.input.value = self.innerHTML.trim();
|
||||
let val = self.innerHTML;
|
||||
self.innerHTML = '';
|
||||
self.input.required = self.hasAttribute('required');
|
||||
self.input.name = self.getAttribute('name');
|
||||
self.appendChild(self.input)
|
||||
self.appendChild(self.label)
|
||||
self.appendChild(self.errorBox);
|
||||
self.input.addMultiListener('change input', self.cb.bind(self));
|
||||
let input = self.input = self.createNew('input', {id: self.id})
|
||||
let label = self.createNew('label', {content: self.dataset.label});
|
||||
self.createNew('span', {classes: 'error', content: self.dataset.error});
|
||||
label.setAttribute('for', self.id);
|
||||
input.type = self.getAttribute('type') || 'text';
|
||||
input.value = val.trim();
|
||||
input.required = self.hasAttribute('required');
|
||||
input.name = self.getAttribute('name');
|
||||
input.addMultiListener('change input', self.cb.bind(self));
|
||||
}
|
||||
|
||||
connectedCallback() {
|
||||
let cl = this.classList;
|
||||
if (this.input.value === "") {
|
||||
cl.remove('focus')
|
||||
} else {
|
||||
cl.add('focus')
|
||||
}
|
||||
this.cb({currentTarget: this.input}, true);
|
||||
}
|
||||
|
||||
cb(e) {
|
||||
cb(e, noInvalid) {
|
||||
let el = e.currentTarget
|
||||
let errorMessage = $('.error-message', el.find('form'));
|
||||
if (errorMessage) {
|
||||
|
@ -475,13 +468,15 @@ class FormHandler {
|
|||
cl.add('valid');
|
||||
cl.remove('invalid');
|
||||
} else {
|
||||
if (!noInvalid) {
|
||||
cl.remove('valid');
|
||||
cl.add('invalid');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define("v-button", VButton);
|
||||
customElements.define("v-input", VInput);
|
||||
|
||||
if ($('#login')) {
|
||||
new FormHandler('form#login', 'body', () => {
|
||||
|
@ -674,3 +669,517 @@ class FormHandler {
|
|||
|
||||
customElements.define("v-editor", VEditor);
|
||||
})();
|
||||
(() => {
|
||||
class Router {
|
||||
constructor(options) {
|
||||
let self = this;
|
||||
self.options = options;
|
||||
document.body.addDelegatedEventListener('click', '[data-link]', (e, el) => {
|
||||
e.preventDefault();
|
||||
$$('[data-link].active').forEach(ex => (ex.classList.remove('active')));
|
||||
let loader = $('.loader').classList;
|
||||
loader.remove('hide');
|
||||
this.handleRouting(el.dataset).then(e => {
|
||||
loader.add('hide');
|
||||
el.classList.add('active');
|
||||
});
|
||||
})
|
||||
document.body.addEventListener('triggerRouter', e => {
|
||||
this.handle(sessionStorage.getItem('url'));
|
||||
})
|
||||
window.addEventListener('popstate', e => {
|
||||
this.handle(e.state);
|
||||
})
|
||||
self.components = {};
|
||||
window.dispatchEvent(new CustomEvent('routerReady'));
|
||||
window.routerIsReady = true;
|
||||
}
|
||||
|
||||
handle(item) {
|
||||
if (!item) {
|
||||
return;
|
||||
}
|
||||
item = JSON.parse(item);
|
||||
this.handleRouting(item.data).then(r => {
|
||||
let it = $('[data-link="' + item.data.link + '"]');
|
||||
if (it) {
|
||||
it.classList.add('active');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async handleRouting(dataset) {
|
||||
try {
|
||||
let url = dataset.link,
|
||||
comp = this.components[url];
|
||||
if (url === "") return null;
|
||||
if (comp) url = comp.getUrl(dataset);
|
||||
let data = await this.handleRequest(url, true); // we know the admin backend only returns json so we cheat a bit :)
|
||||
if (data.reload) {
|
||||
return location.reload();
|
||||
}
|
||||
comp = comp || this.components[data.component] || null;
|
||||
if (comp) {
|
||||
sessionStorage.setItem('url', JSON.stringify({data: dataset}));
|
||||
comp.handle(data, dataset).then(r => {
|
||||
$(this.options.toReplace).innerHTML = r;
|
||||
history.pushState(JSON.stringify({data: dataset}), document.title);
|
||||
});
|
||||
} else {
|
||||
await alert("Error");
|
||||
}
|
||||
return null;
|
||||
} catch (e) {
|
||||
return e;
|
||||
}
|
||||
}
|
||||
|
||||
async handleRequest(url, forceJSON) {
|
||||
url = url.trim();
|
||||
if (url === '') return;
|
||||
// await ;)
|
||||
return await fetch(url, {
|
||||
credentials: "same-origin"
|
||||
}).then(res => {
|
||||
if (!res.ok) {
|
||||
throw `URL is Status: ${res.status}`;
|
||||
}
|
||||
let c = res.headers.get("Content-Type");
|
||||
if (c.indexOf('json') !== -1 || forceJSON) return res.json();
|
||||
if (c.indexOf('text') !== -1) return res.text();
|
||||
return res.blob();
|
||||
}).catch(err => {
|
||||
console.error(err)
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
addComponent(name, component) {
|
||||
this.components[name] = component;
|
||||
}
|
||||
}
|
||||
|
||||
window.router = new Router({
|
||||
toReplace: '.content-area'
|
||||
})
|
||||
})();
|
||||
'use strict';
|
||||
|
||||
class VTpeLCore {
|
||||
constructor(options = {}) {
|
||||
this.templates = {};
|
||||
this.dir = options.path || '/tpl/';
|
||||
this.suffix = options.suffix || 'tpl';
|
||||
this.path = options.template || `${this.dir}%s.${this.suffix}`;
|
||||
this.cache = options.cache === undefined ? true : options.cache;
|
||||
}
|
||||
|
||||
async loadTemplate(name) {
|
||||
if (this.templates[name]) {
|
||||
return null;
|
||||
}
|
||||
let path = this.path.replace('%s', name);
|
||||
let rawData = await fetch(path, {cache: "force-cache"});
|
||||
if (rawData.ok) {
|
||||
let data = await rawData.text();
|
||||
this.addTpl(name, data);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
async loadArray(names) {
|
||||
for (let name of names) {
|
||||
await this.loadTemplate(name);
|
||||
}
|
||||
}
|
||||
|
||||
addTpl(name, content) {
|
||||
let temp = this.templates[name] = new VTpeLTemplate(name, content, this);
|
||||
temp.parseContent(this.cache);
|
||||
}
|
||||
|
||||
async renderOn(name, data) {
|
||||
if (this.templates[name]) {
|
||||
return await this.templates[name].render(data);
|
||||
}
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
'use strict';
|
||||
|
||||
const VParserTypes = {
|
||||
content: 0,
|
||||
variable: 1,
|
||||
for: 2,
|
||||
forEach: 3,
|
||||
forContent: 4,
|
||||
forEnd: 5,
|
||||
if: 6,
|
||||
ifContent: 7,
|
||||
ifEnd: 8,
|
||||
assign: 9,
|
||||
include: 10,
|
||||
none: -1,
|
||||
};
|
||||
|
||||
|
||||
class VTpeLParser {
|
||||
constructor(name, content) {
|
||||
let self = this;
|
||||
self.name = name;
|
||||
self.legex = content.trim();
|
||||
self.index = 0;
|
||||
self.content = '';
|
||||
self.parsed = [];
|
||||
self.contexts = [0];
|
||||
}
|
||||
|
||||
tokenize() {
|
||||
let self = this;
|
||||
for (self.index = 0; self.index < self.legex.length; self.index++) {
|
||||
let i = self.index,
|
||||
char = self.legex.charAt(i);
|
||||
if (self.nextContains('/*', i, true)) {
|
||||
self.extract('*/', VParserTypes.none)
|
||||
} else if (self.nextContains('// ', i, true)) {
|
||||
self.extract("\n", VParserTypes.none);
|
||||
} else if (self.nextContains('<!--', i, true)) {
|
||||
self.extract('-->', VParserTypes.none);
|
||||
} else if (self.nextContains('{for(', i, true)) {
|
||||
self.extract(')}', VParserTypes.for);
|
||||
self.contexts.push(VParserTypes.for);
|
||||
} else if (self.nextContains('{include(', i, true)) {
|
||||
self.extract(')}', VParserTypes.include);
|
||||
self.contexts.push(VParserTypes.include);
|
||||
} else if (self.nextContains('{foreach(', i, true)) {
|
||||
self.extract(')}', VParserTypes.forEach);
|
||||
self.contexts.push(VParserTypes.forEach);
|
||||
} else if (self.nextContains('{/for}', i, true)) {
|
||||
self.addType(VParserTypes.forEnd);
|
||||
self.contexts.pop();
|
||||
} else if (self.nextContains('{if(', i, true)) {
|
||||
self.extract(')}', VParserTypes.if);
|
||||
self.contexts.push(VParserTypes.if);
|
||||
} else if (self.nextContains('{/if}', i, true)) {
|
||||
self.addType(VParserTypes.ifEnd);
|
||||
self.contexts.pop();
|
||||
} else if (self.nextContains('$${', i, true)) {
|
||||
self.extract('}', VParserTypes.assign);
|
||||
} else if (self.nextContains('${', i, true)) {
|
||||
self.extract('}', VParserTypes.variable);
|
||||
} else {
|
||||
self.content += char;
|
||||
}
|
||||
}
|
||||
self.addType(VParserTypes.content);
|
||||
return self.parsed;
|
||||
}
|
||||
|
||||
addType(type) {
|
||||
let self = this;
|
||||
let content = self.content.replace(/^\n+|\n+$/g, ''),
|
||||
instructions = self.findInstructions(type);
|
||||
self.content = '';
|
||||
if (type !== VParserTypes.none) {
|
||||
if (type === VParserTypes.content && content === '') {
|
||||
return null;
|
||||
}
|
||||
return self.parsed.push({
|
||||
content: content,
|
||||
type: type,
|
||||
context: self.contexts[self.contexts.length - 1],
|
||||
instructions: instructions
|
||||
});
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
nextContains(find, index, add = false) {
|
||||
let count = this.nextContainsRaw(this.legex, find, index);
|
||||
if (add && count > 0) {
|
||||
this.index += count;
|
||||
}
|
||||
return count > 0 || count === -1;
|
||||
}
|
||||
|
||||
nextContainsRaw(raw, find, index) {
|
||||
if (typeof find === "string") {
|
||||
find = find.split("");
|
||||
}
|
||||
let count = find.length;
|
||||
if (count < 1) {
|
||||
return -1;
|
||||
}
|
||||
for (let i = 0; i < count; i++) {
|
||||
let nc = raw.charAt(index + i);
|
||||
if ((find[i] === '\n' && nc === undefined)) {
|
||||
return count;
|
||||
}
|
||||
if (find[i] !== nc) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
extract(findUntil = '}', type = 1) {
|
||||
let self = this;
|
||||
self.addType(0);
|
||||
findUntil = findUntil.split("")
|
||||
let content = '',
|
||||
index = self.index,
|
||||
legex = self.legex,
|
||||
firstFind = findUntil.shift();
|
||||
for (let i = self.index; i < legex.length; i++) {
|
||||
let char = legex.charAt(i);
|
||||
if (char === firstFind && self.nextContains(findUntil, i + 1)) {
|
||||
console.debug(`[Parser][${index} > ${i}] >> ${content}`);
|
||||
self.index = i + findUntil.length;
|
||||
self.content = content.trim();
|
||||
self.addType(type);
|
||||
return;
|
||||
}
|
||||
content += char;
|
||||
}
|
||||
if (firstFind === "\n") {
|
||||
self.index = legex.length;
|
||||
self.content = content.trim();
|
||||
self.addType(type);
|
||||
return
|
||||
}
|
||||
throw 'Template variable at Position: ' + index + ' not closed!';
|
||||
}
|
||||
|
||||
// @todo implement split... is needed for if statements and math stuff or get it stupid!
|
||||
getOperator(string) {
|
||||
let operators = [];
|
||||
for (let i = 0; i < string.length; i++) {
|
||||
if (this.nextContainsRaw(string, "(", i)) {
|
||||
let innerCon = '';
|
||||
for (let x = 0; x < string.length; x++) {
|
||||
let char = string.charAt(i + x);
|
||||
if (char === ')') {
|
||||
break;
|
||||
}
|
||||
innerCon += char;
|
||||
}
|
||||
operators = [...operators, this.getOperator(innerCon)];
|
||||
} else {
|
||||
}
|
||||
}
|
||||
return operators;
|
||||
}
|
||||
|
||||
findInstructions(type) {
|
||||
if (type === VParserTypes.if) {
|
||||
return this.getOperator(this.content);
|
||||
}
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
'use strict';
|
||||
|
||||
class VTepLInterpreter {
|
||||
constructor(parser, core) {
|
||||
this.parser = parser;
|
||||
this.data = [];
|
||||
this.content = '';
|
||||
this.core = core;
|
||||
}
|
||||
|
||||
async render(data) {
|
||||
let self = this;
|
||||
self.data = data;
|
||||
let newData = await self.interpreter(self.parser.parsed);
|
||||
self.data = [];
|
||||
return newData[0];
|
||||
}
|
||||
|
||||
async interpreter(parsed, index = 0) {
|
||||
let self = this;
|
||||
let types = VParserTypes;
|
||||
let tplCont = '';
|
||||
for (let i = index; i < parsed.length; i++) {
|
||||
let item = parsed[i],
|
||||
content = item.content;
|
||||
switch (item.type) {
|
||||
case types.content:
|
||||
tplCont += content;
|
||||
break;
|
||||
case types.variable:
|
||||
tplCont += self.getVariable(content)
|
||||
break;
|
||||
case types.assign:
|
||||
let data = content.split("="),
|
||||
key = data.shift();
|
||||
self.setVariable(data.join("=").trim(), key.trim());
|
||||
break;
|
||||
case types.forEach:
|
||||
let d = await this.handleForEach(item, parsed, i);
|
||||
i = d[0];
|
||||
tplCont += d[1];
|
||||
break;
|
||||
case types.for:
|
||||
let fd = await this.handleFor(item, parsed, i);
|
||||
i = fd[0];
|
||||
tplCont += fd[1];
|
||||
break;
|
||||
case types.if:
|
||||
let id = await this.handleIf(item, parsed, i);
|
||||
i = id[0];
|
||||
tplCont += id[1];
|
||||
break;
|
||||
case types.include:
|
||||
tplCont += await this.handleInclude(item);
|
||||
break;
|
||||
case types.ifEnd:
|
||||
tplCont += content;
|
||||
return [tplCont, i];
|
||||
case types.forEnd:
|
||||
tplCont += content;
|
||||
return [tplCont, i];
|
||||
default:
|
||||
console.warn("Invalid Type found");
|
||||
break;
|
||||
}
|
||||
}
|
||||
return [tplCont, parsed.length];
|
||||
}
|
||||
|
||||
getVariable(variable) {
|
||||
variable = variable.toString();
|
||||
if (this.data[variable]) {
|
||||
return this.data[variable];
|
||||
}
|
||||
let split = variable.split("."),
|
||||
prevVar = this.data;
|
||||
for (let i = 0; i < split.length; i++) {
|
||||
prevVar = prevVar[split[i]] || prevVar;
|
||||
}
|
||||
if (typeof prevVar === 'string') {
|
||||
return prevVar;
|
||||
}
|
||||
if (typeof prevVar === 'number') {
|
||||
return prevVar.toString();
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
setVariable(value, variable) {
|
||||
let c = this.getVariable(value);
|
||||
if (c !== '') {
|
||||
value = c;
|
||||
}
|
||||
this.data[variable] = value;
|
||||
}
|
||||
|
||||
async handleForEach(item, parsed, i) {
|
||||
let content = item.content.split(" as ");
|
||||
let root = this.getVariable(content[0].trim());
|
||||
let addTo = 0,
|
||||
isInvalid = false;
|
||||
if (root === '') {
|
||||
isInvalid = true;
|
||||
root = {invalid: "true"};
|
||||
}
|
||||
let d = Object.keys(root),
|
||||
raw = '',
|
||||
varContent = content[1].trim().split(",");
|
||||
for (let x of d) {
|
||||
if (varContent.length === 2) {
|
||||
this.setVariable(x, varContent[1]);
|
||||
}
|
||||
this.setVariable(root[x], varContent[0]);
|
||||
let data = await this.interpreter(parsed, i + 1);
|
||||
addTo = data[1];
|
||||
raw += data[0];
|
||||
}
|
||||
if (isInvalid) {
|
||||
raw = '';
|
||||
}
|
||||
return [addTo, raw];
|
||||
}
|
||||
|
||||
async handleInclude(item) {
|
||||
let split = item.content.split(";"),
|
||||
name = split.shift(),
|
||||
data = {};
|
||||
await this.core.loadTemplate(name);
|
||||
for (let item of split) {
|
||||
let d = item.split("="),
|
||||
index = d.shift(),
|
||||
dat = d.join("=");
|
||||
if (dat.startsWith("$")) {
|
||||
dat = this.getVariable(dat.substr(1, dat.length));
|
||||
}
|
||||
data[index] = dat;
|
||||
}
|
||||
return await this.core.renderOn(name, data);
|
||||
}
|
||||
|
||||
async handleFor(item, parsed, ind) {
|
||||
let content = item.content.split(" as "),
|
||||
addTo = 0,
|
||||
count = content[0].trim().split(".."),
|
||||
max = parseInt(count[1]),
|
||||
min = parseInt(count[0]),
|
||||
newContent = '';
|
||||
for (let i = min; i < max; i++) {
|
||||
this.setVariable(i.toString(), content[1]);
|
||||
let data = await this.interpreter(parsed, ind + 1);
|
||||
addTo = data[1];
|
||||
newContent += data[0];
|
||||
}
|
||||
return [addTo, newContent];
|
||||
}
|
||||
|
||||
async handleIf(item, parsed, i) {
|
||||
let data = await this.interpreter(parsed, i + 1);
|
||||
return [data[1], data[0]];
|
||||
}
|
||||
}
|
||||
|
||||
'use strict';
|
||||
|
||||
class VTpeLTemplate {
|
||||
constructor(name, content, core) {
|
||||
this.name = name;
|
||||
this.tpl = content;
|
||||
this.parser = new VTpeLParser(name, content);
|
||||
this.core = core;
|
||||
}
|
||||
|
||||
async render(data = {}) {
|
||||
return await new VTepLInterpreter(this.parser, this.core).render(data);
|
||||
}
|
||||
|
||||
parseContent(cache) {
|
||||
if (cache) {
|
||||
let storage = localStorage.getItem(this.name);
|
||||
if (storage) {
|
||||
this.parser.parsed = JSON.parse(storage);
|
||||
return;
|
||||
}
|
||||
}
|
||||
this.parser.tokenize();
|
||||
if (cache) {
|
||||
localStorage.setItem(this.name, JSON.stringify(this.parser.parsed));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
(() => {
|
||||
window.tpl = new VTpeLCore({
|
||||
template: '/admin/api/templateLoader?tpl=%s',
|
||||
cache: !document.body.hasAttribute('debug')
|
||||
});
|
||||
//preload includes to make sure they are loaded always :)
|
||||
window.tpl.loadArray([
|
||||
'includes/btn',
|
||||
'includes/input',
|
||||
'includes/select',
|
||||
'includes/svg',
|
||||
])
|
||||
})();
|
2
public/theme/admin/js/scripts.min.js
vendored
|
@ -38,6 +38,8 @@ class AdminController implements RenderController
|
|||
}
|
||||
Asset::get()->addCSS('styles', 'style.css', 1);
|
||||
Asset::get()->addJS('scripts', 'scripts.min.js', 1);
|
||||
// Components are the Rendering-Pipeline to know how each Admin-Component needs to be rendered
|
||||
Asset::get()->addJS('components', 'components.min.js', 5);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@ namespace Venom\Admin;
|
|||
|
||||
|
||||
use Venom\Admin\Routes\LoginRoute;
|
||||
use Venom\Admin\Routes\TemplateLoader;
|
||||
use Venom\Routing\Router;
|
||||
use Venom\Venom;
|
||||
|
||||
|
@ -12,9 +13,7 @@ class AdminRouterInit
|
|||
{
|
||||
public static function registerAdminRouters(Venom $venom): void
|
||||
{
|
||||
$router = new Router(Router::ADMIN_ROUTER, 1.0, '/admin/api');
|
||||
$router->addRoutes(self::getRoutes());
|
||||
$venom->addRouter(Router::ADMIN_ROUTER, $router);
|
||||
$venom->getRouter(Router::ADMIN_ROUTER)->addRoutes(self::getRoutes());
|
||||
}
|
||||
|
||||
public static function getRoutes(): array
|
||||
|
@ -31,6 +30,15 @@ class AdminRouterInit
|
|||
"GET" => 'handle'
|
||||
]
|
||||
]
|
||||
],
|
||||
'/templateLoader' => [
|
||||
'cl' => TemplateLoader::class,
|
||||
'roles' => ['ROLE_GUEST'],
|
||||
'routes' => [
|
||||
'*' => [
|
||||
"GET" => 'handle'
|
||||
]
|
||||
]
|
||||
]
|
||||
];
|
||||
}
|
||||
|
|
|
@ -21,8 +21,7 @@ class LoginRoute implements Route
|
|||
{
|
||||
if ($fnc === 'logout') {
|
||||
Security::get()->logout();
|
||||
$url = ArgumentHandler::get()->getPostItem('REDIRECT_TO', '/admin/');
|
||||
header('Location: ' . $url);
|
||||
echo '{"reload": true}';
|
||||
die();
|
||||
}
|
||||
return true;
|
||||
|
|
23
src/Venom/Admin/Routes/TemplateLoader.php
Normal file
|
@ -0,0 +1,23 @@
|
|||
<?php
|
||||
|
||||
|
||||
namespace Venom\Admin\Routes;
|
||||
|
||||
use Venom\Core\ArgumentHandler;
|
||||
use Venom\Helper\TemplateUtil;
|
||||
use Venom\Routing\Route;
|
||||
|
||||
class TemplateLoader implements Route
|
||||
{
|
||||
public function handle(): bool
|
||||
{
|
||||
header('Expires: ' . gmdate('D, d M Y H:i:s \G\M\T', time() + (60 * 60 * 60 * 30)));
|
||||
header('Cache-Control: public');
|
||||
$id = ArgumentHandler::get()->getItem('tpl', '..');
|
||||
if (strpos($id, '..')) {
|
||||
return false;
|
||||
}
|
||||
echo TemplateUtil::includeTemplate('jsTemplates/' . $id, '.tpl');
|
||||
die();
|
||||
}
|
||||
}
|
|
@ -4,9 +4,11 @@
|
|||
namespace Venom\Core;
|
||||
|
||||
|
||||
use Venom\Venom;
|
||||
|
||||
interface Module
|
||||
{
|
||||
public function register(): bool;
|
||||
public function register(Venom $venom): bool;
|
||||
|
||||
public function init(): void;
|
||||
}
|
25
src/Venom/Helper/AdminHelper.php
Normal file
|
@ -0,0 +1,25 @@
|
|||
<?php
|
||||
|
||||
|
||||
namespace Venom\Helper;
|
||||
|
||||
|
||||
class AdminHelper
|
||||
{
|
||||
public static function getResponse($content, bool $shouldReload = false, string $component = '', $extra = false)
|
||||
{
|
||||
$response = [
|
||||
'content' => $content,
|
||||
'component' => $component
|
||||
];
|
||||
|
||||
if ($shouldReload) {
|
||||
$response['reload'] = true;
|
||||
}
|
||||
if ($extra) {
|
||||
$response['extra'] = $extra;
|
||||
}
|
||||
echo json_encode($response);
|
||||
die();
|
||||
}
|
||||
}
|
58
src/Venom/Helper/TemplateUtil.php
Normal file
|
@ -0,0 +1,58 @@
|
|||
<?php
|
||||
|
||||
|
||||
namespace Venom\Helper;
|
||||
|
||||
|
||||
use Venom\Core\Config;
|
||||
|
||||
class TemplateUtil
|
||||
{
|
||||
private static ?TemplateUtil $instance = null;
|
||||
private string $baseTemplate;
|
||||
private string $templateDir;
|
||||
|
||||
private function __construct()
|
||||
{
|
||||
if (Config::getInstance()->isAdmin()) {
|
||||
$base = 'base';
|
||||
$theme = 'admin';
|
||||
} else {
|
||||
$data = Config::getInstance()->getRenderer();
|
||||
$theme = $data->theme;
|
||||
$base = $data->baseFile ?? 'base';
|
||||
}
|
||||
$this->baseTemplate = $base . '.php';
|
||||
$this->templateDir = __DIR__ . '/../../../tpl/' . $theme . '/';
|
||||
}
|
||||
|
||||
public static function getInstance(): TemplateUtil
|
||||
{
|
||||
if (self::$instance === null) {
|
||||
self::$instance = new TemplateUtil();
|
||||
}
|
||||
return self::$instance;
|
||||
}
|
||||
|
||||
public function getDir(): string
|
||||
{
|
||||
return $this->templateDir;
|
||||
}
|
||||
|
||||
public function getBase(): string
|
||||
{
|
||||
return $this->baseTemplate;
|
||||
}
|
||||
|
||||
public static function includeTemplate($template, $suffix = '.php')
|
||||
{
|
||||
$dir = self::getInstance()->getDir();
|
||||
$template .= $suffix;
|
||||
if (file_exists($dir . $template)) {
|
||||
ob_start();
|
||||
include_once $dir . $template;
|
||||
return ob_get_clean();
|
||||
}
|
||||
return '';
|
||||
}
|
||||
}
|
|
@ -27,7 +27,7 @@ class Router
|
|||
|
||||
public function addRoutes(array $routes): void
|
||||
{
|
||||
$this->routes = $routes;
|
||||
$this->routes = array_merge($this->routes, $routes);
|
||||
}
|
||||
|
||||
public function addRoute(string $path, array $route): void
|
||||
|
|
|
@ -29,6 +29,7 @@ class Venom
|
|||
{
|
||||
ExceptionHandler::setExceptionHandler();
|
||||
$this->renderer = new VenomRenderer($this);
|
||||
$this->routers[Router::ADMIN_ROUTER] = new Router(Router::ADMIN_ROUTER, 1.0, '/admin/api');
|
||||
Asset::get()->setRenderer($this->renderer);
|
||||
}
|
||||
|
||||
|
@ -116,7 +117,7 @@ class Venom
|
|||
{
|
||||
foreach ($modules as $key => $moduleClass) {
|
||||
$module = new $moduleClass;
|
||||
if ($module instanceof Module && $module->register()) {
|
||||
if ($module instanceof Module && $module->register($this)) {
|
||||
$this->modules[$key] = $module;
|
||||
}
|
||||
}
|
||||
|
@ -131,4 +132,9 @@ class Venom
|
|||
{
|
||||
$this->routers[$name] = $router;
|
||||
}
|
||||
|
||||
public function getRouter(string $router): ?Router
|
||||
{
|
||||
return $this->routers[$router];
|
||||
}
|
||||
}
|
|
@ -7,6 +7,7 @@ namespace Venom\Views;
|
|||
use Venom\Core\ArgumentHandler;
|
||||
use Venom\Core\Config;
|
||||
use Venom\Helper\MetaGenerator;
|
||||
use Venom\Helper\TemplateUtil;
|
||||
use Venom\Venom;
|
||||
|
||||
class VenomRenderer
|
||||
|
@ -52,8 +53,7 @@ class VenomRenderer
|
|||
public function renderTemplate($template): void
|
||||
{
|
||||
// random variable name... to remove it instantly
|
||||
echo $this->includeTemplate($template, '1408138186');
|
||||
unset($this->vars['1408138186']);
|
||||
echo TemplateUtil::includeTemplate($template);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -64,17 +64,10 @@ class VenomRenderer
|
|||
*/
|
||||
public function includeTemplate($template, $varName = '')
|
||||
{
|
||||
$template .= '.php';
|
||||
if (file_exists($this->templateDir . $template)) {
|
||||
ob_start();
|
||||
include_once $this->templateDir . $template;
|
||||
$data = ob_get_clean();
|
||||
$data = TemplateUtil::includeTemplate($template);
|
||||
$this->vars[$varName] = $data;
|
||||
return $data;
|
||||
}
|
||||
$this->vars[$varName] = '';
|
||||
return '';
|
||||
}
|
||||
|
||||
public function addVar($name, $value): void
|
||||
{
|
||||
|
@ -94,17 +87,12 @@ class VenomRenderer
|
|||
public function init(?RenderController $controller): void
|
||||
{
|
||||
$this->controller = $controller;
|
||||
$data = Config::getInstance()->getRenderer();
|
||||
$theme = $data->theme;
|
||||
$base = $data->baseFile ?? 'base';
|
||||
if (!Config::getInstance()->isAdmin()) {
|
||||
$this->metaGenerator = new MetaGenerator();
|
||||
if (Config::getInstance()->isAdmin()) {
|
||||
$base = 'base';
|
||||
$theme = 'admin';
|
||||
} else {
|
||||
$this->metaGenerator->loadById();
|
||||
}
|
||||
$this->baseTemplate = $base . '.php';
|
||||
$this->templateDir = __DIR__ . '/../../../tpl/' . $theme . '/';
|
||||
$util = TemplateUtil::getInstance();
|
||||
$this->templateDir = $util->getDir();
|
||||
$this->baseTemplate = $util->getBase();
|
||||
}
|
||||
}
|
||||
|
|
79
src/modules/RoleModule.php
Normal file
|
@ -0,0 +1,79 @@
|
|||
<?php
|
||||
|
||||
|
||||
namespace Modules;
|
||||
|
||||
|
||||
use Venom\Core\Config;
|
||||
use Venom\Core\Module;
|
||||
use Venom\Helper\AdminHelper;
|
||||
use Venom\Routing\Route;
|
||||
use Venom\Routing\Router;
|
||||
use Venom\Venom;
|
||||
|
||||
class RoleModule implements Module, Route
|
||||
{
|
||||
|
||||
public function register(Venom $venom): bool
|
||||
{
|
||||
if (Config::getInstance()->isAdmin()) {
|
||||
$this->registerAdminRoutes($venom);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public function init(): void
|
||||
{
|
||||
}
|
||||
|
||||
private function registerAdminRoutes(Venom $venom)
|
||||
{
|
||||
$venom->getRouter(Router::ADMIN_ROUTER)->addRoutes([
|
||||
'/roles' => [
|
||||
'cl' => RoleModule::class,
|
||||
'roles' => ['ROLE_ADMIN'],
|
||||
'routes' => [
|
||||
'*' => [
|
||||
"GET" => 'get',
|
||||
],
|
||||
'1' => [
|
||||
"GET" => 'getById',
|
||||
"POST" => 'update',
|
||||
"PUT" => 'insert',
|
||||
]
|
||||
]
|
||||
]
|
||||
]);
|
||||
}
|
||||
|
||||
public function get()
|
||||
{
|
||||
AdminHelper::getResponse([
|
||||
'roles' => [
|
||||
['id' => 1, 'name' => 'Admin', 'icon' => 'vt-visibility'],
|
||||
['id' => 2, 'name' => 'Moderator', 'icon' => 'vt-edit'],
|
||||
['id' => 3, 'name' => 'Gast', 'icon' => 'vt-edit'],
|
||||
]
|
||||
]);
|
||||
}
|
||||
|
||||
public function update(): bool
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public function insert(): bool
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public function getById($id)
|
||||
{
|
||||
AdminHelper::getResponse([
|
||||
'caseName' => 'ROLE_ADMIN',
|
||||
'id' => $id,
|
||||
'name' => 'Admin',
|
||||
'icon' => 'vt-visibility'
|
||||
]);
|
||||
}
|
||||
}
|
|
@ -4,531 +4,17 @@
|
|||
<div data-link="">Meta Data</div>
|
||||
<div data-link="" class="active">Overview</div>
|
||||
<div data-link="">Pages</div>
|
||||
<div data-link="">Roles</div>
|
||||
<div data-link="/roles">Roles</div>
|
||||
<div data-link="">SEO-URL</div>
|
||||
<div data-link="">Users</div>
|
||||
<div data-link="">Venom-Status</div>
|
||||
<div data-link="/admin/api/login/logout">Ausloggen</div>
|
||||
</nav>
|
||||
<div class="content-area">
|
||||
<v-table>
|
||||
<v-table-header>
|
||||
<h2>Roles</h2>
|
||||
</v-table-header>
|
||||
<v-table-row>
|
||||
<div>
|
||||
<h3>Overview</h3>
|
||||
<div class="icon-cont">
|
||||
<div class="icon icon-visibility"></div>
|
||||
<div class="text">Admin (secured)</div>
|
||||
</div>
|
||||
<div class="icon-cont">
|
||||
<div class="icon icon-edit"></div>
|
||||
<div class="text">Moderator</div>
|
||||
</div>
|
||||
<div class="icon-cont">
|
||||
<div class="icon icon-edit"></div>
|
||||
<div class="text">Predator</div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<h3>Add new Role</h3>
|
||||
<v-button
|
||||
class="input-group"
|
||||
data-label="New Role Name"
|
||||
id="new-role-name"
|
||||
required
|
||||
name="newRoleName"
|
||||
data-error="New Role Name is required"></v-button>
|
||||
<button class="btn btn--primary">
|
||||
<span class="btn-ripple"></span>
|
||||
<span class="btn__content">Add</span>
|
||||
</button>
|
||||
</div>
|
||||
</v-table-row>
|
||||
<v-table-footer>
|
||||
Dies ist der Footer.
|
||||
</v-table-footer>
|
||||
</v-table>
|
||||
|
||||
|
||||
<v-table>
|
||||
<v-table-header>
|
||||
<h2>Role: Admin</h2>
|
||||
</v-table-header>
|
||||
<v-table-row>
|
||||
<div class="icon-cont">
|
||||
<div class="icon icon-arrow-back"></div>
|
||||
</div>
|
||||
</v-table-row>
|
||||
<v-table-row>
|
||||
<div>
|
||||
<h3>Role Status</h3>
|
||||
<div class="switch fix-pad">
|
||||
<input type="checkbox" id="role-active" required name="roleActive">
|
||||
<label for="role-active"></label>
|
||||
<span>If enabled role is active.</span>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<h3>Role Name</h3>
|
||||
<v-button
|
||||
class="input-group"
|
||||
data-label="Change Name"
|
||||
id="change-role-name"
|
||||
required
|
||||
name="roleName"
|
||||
data-error="Role Name is required"></v-button>
|
||||
</div>
|
||||
</v-table-row>
|
||||
<v-table-row>
|
||||
<div class="modules">
|
||||
<h4>Module</h4>
|
||||
<div>Meta-Data</div>
|
||||
<div>Pages</div>
|
||||
<div>Roles</div>
|
||||
<div>SEO-URL</div>
|
||||
<div>Users</div>
|
||||
<div>VENOM-Status</div>
|
||||
</div>
|
||||
<div>
|
||||
<h4>Edit</h4>
|
||||
<div class="switch">
|
||||
<input type="checkbox" id="permission-edit-meta-data" required
|
||||
name="permissionEditMetaData">
|
||||
<label for="permission-edit-meta-data"></label>
|
||||
<span></span>
|
||||
</div>
|
||||
<div class="switch">
|
||||
<input type="checkbox" id="permission-edit-pages" required name="permissionEditPages">
|
||||
<label for="permission-edit-pages"></label>
|
||||
<span></span>
|
||||
</div>
|
||||
<div class="switch">
|
||||
<input type="checkbox" id="permission-edit-roles" required name="permissionEditRoles">
|
||||
<label for="permission-edit-roles"></label>
|
||||
<span></span>
|
||||
</div>
|
||||
<div class="switch">
|
||||
<input type="checkbox" id="permission-edit-seo-url" required name="permissionEditSeoUrl">
|
||||
<label for="permission-edit-seo-url"></label>
|
||||
<span></span>
|
||||
</div>
|
||||
<div class="switch">
|
||||
<input type="checkbox" id="permission-edit-label" required name="permissionEditUsers">
|
||||
<label for="permission-edit-label"></label>
|
||||
<span></span>
|
||||
</div>
|
||||
<div class="switch">
|
||||
<input type="checkbox" id="permission-edit-venom-status" required
|
||||
name="permissionEditVenomStatus">
|
||||
<label for="permission-edit-venom-status"></label>
|
||||
<span></span>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<h4>View</h4>
|
||||
<div class="switch">
|
||||
<input type="checkbox" id="permission-view-meta-data" required
|
||||
name="permissionViewMetaData">
|
||||
<label for="permission-view-meta-data"></label>
|
||||
<span></span>
|
||||
</div>
|
||||
|
||||
<div class="switch">
|
||||
<input type="checkbox" id="permission-view-pages" required name="permissionViewPages">
|
||||
<label for="permission-view-pages"></label>
|
||||
<span></span>
|
||||
</div>
|
||||
|
||||
<div class="switch">
|
||||
<input type="checkbox" id="permission-view-roles" required name="permissionViewRoles">
|
||||
<label for="permission-view-roles"></label>
|
||||
<span></span>
|
||||
</div>
|
||||
|
||||
<div class="switch">
|
||||
<input type="checkbox" id="permission-view-seo-url" required name="permissionViewSeoUrl">
|
||||
<label for="permission-view-seo-url"></label>
|
||||
<span></span>
|
||||
</div>
|
||||
|
||||
<div class="switch">
|
||||
<input type="checkbox" id="permission-view-label" required name="permissionViewUsers">
|
||||
<label for="permission-view-label"></label>
|
||||
<span></span>
|
||||
</div>
|
||||
|
||||
<div class="switch">
|
||||
<input type="checkbox" id="permission-view-venom-status" required
|
||||
name="permissionViewVenomStatus">
|
||||
<label for="permission-view-venom-status"></label>
|
||||
<span></span>
|
||||
</div>
|
||||
</div>
|
||||
</v-table-row>
|
||||
<v-table-row class="btn-line">
|
||||
<div>
|
||||
<button class="btn btn--valid">
|
||||
<span class="btn-ripple"></span>
|
||||
<span class="btn__content">Save</span>
|
||||
</button>
|
||||
<button class="btn btn--primary">
|
||||
<span class="btn-ripple"></span>
|
||||
<span class="btn__content">Reset</span>
|
||||
</button>
|
||||
<button class="btn btn--warn">
|
||||
<span class="btn-ripple"></span>
|
||||
<span class="btn__content">Delete Role</span>
|
||||
</button>
|
||||
</div>
|
||||
</v-table-row>
|
||||
<v-table-footer>
|
||||
Dies ist der Footer.
|
||||
</v-table-footer>
|
||||
</v-table>
|
||||
|
||||
|
||||
<v-table>
|
||||
<v-table-header>
|
||||
<h2>Users</h2>
|
||||
</v-table-header>
|
||||
<v-table-row>
|
||||
<div>
|
||||
<h3>All Users</h3>
|
||||
<div class="icon-cont">
|
||||
<div class="icon icon-edit"></div>
|
||||
<div class="text">ALLE Alphabetisch ordnen!</div>
|
||||
</div>
|
||||
<div class="icon-cont">
|
||||
<div class="icon icon-edit"></div>
|
||||
<div class="text">derflieger (Ina Ruh)</div>
|
||||
</div>
|
||||
<div class="icon-cont">
|
||||
<div class="icon icon-edit"></div>
|
||||
<div class="text">engineertrooper (Dominic Seela)</div>
|
||||
</div>
|
||||
<div class="icon-cont">
|
||||
<div class="icon icon-edit"></div>
|
||||
<div class="text">versustunez (Maurice Grönwoldt)</div>
|
||||
</div>
|
||||
<div class="icon-cont">
|
||||
<div class="icon icon-edit"></div>
|
||||
<div class="text">vollglaswasser (Marie Joseph)</div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<h3>Add new User</h3>
|
||||
<div class="add-new-role max-width">
|
||||
<v-button
|
||||
class="input-group"
|
||||
data-label="New User Name"
|
||||
required
|
||||
name="newUserName"
|
||||
data-error="New User Name is required"></v-button>
|
||||
</div>
|
||||
<button class="btn btn--primary">
|
||||
<span class="btn-ripple"></span>
|
||||
<span class="btn__content">Add User</span>
|
||||
</button>
|
||||
</div>
|
||||
</v-table-row>
|
||||
<v-table-footer>
|
||||
Dies ist der Footer.
|
||||
</v-table-footer>
|
||||
</v-table>
|
||||
|
||||
|
||||
<v-table>
|
||||
<v-table-header>
|
||||
<h2>User: engineertrooper</h2>
|
||||
</v-table-header>
|
||||
<v-table-row>
|
||||
<div class="icon-cont">
|
||||
<div class="icon icon-arrow-back"></div>
|
||||
</div>
|
||||
</v-table-row>
|
||||
<v-table-row>
|
||||
<div>
|
||||
<v-button
|
||||
class="input-group"
|
||||
data-label="Username"
|
||||
required
|
||||
name="newUserName"
|
||||
data-error="New User Name is required">EngineerTrooper
|
||||
</v-button>
|
||||
<v-button
|
||||
class="input-group"
|
||||
data-label="Author Name"
|
||||
required
|
||||
name="newAuthorName"
|
||||
data-error="Author Name is required">Dominic Seela
|
||||
</v-button>
|
||||
</div>
|
||||
<div>
|
||||
<v-button
|
||||
class="input-group"
|
||||
data-label="E-Mail"
|
||||
required
|
||||
name="newEMailAddress"
|
||||
data-error="E-Mail Address is required">kontakt@engineertrooper.com
|
||||
</v-button>
|
||||
</div>
|
||||
<div>
|
||||
<v-button
|
||||
class="input-group"
|
||||
data-label="E-Mail"
|
||||
required
|
||||
name="newEMailAddress"
|
||||
data-error="E-Mail Address is required">kontakt@engineertrooper.com
|
||||
</v-button>
|
||||
<div class="description">New Password:</div>
|
||||
<div class="input-group">
|
||||
<input id="new-password" required name="newPassword">
|
||||
<label for="new-password"></label>
|
||||
<span class="error">New Password is required</span>
|
||||
</div>
|
||||
<div class="description">New Password repeat:</div>
|
||||
<div class="input-group">
|
||||
<input id="new-password-repeat" required name="newPasswordRepeat">
|
||||
<label for="new-password-repeat"></label>
|
||||
<span class="error">New Password Repeat is required</span>
|
||||
</div>
|
||||
</div>
|
||||
</v-table-row>
|
||||
<v-table-row class="btn-line">
|
||||
<div>
|
||||
<button class="btn btn--valid">
|
||||
<span class="btn-ripple"></span>
|
||||
<span class="btn__content">Save</span>
|
||||
</button>
|
||||
<button class="btn btn--primary">
|
||||
<span class="btn-ripple"></span>
|
||||
<span class="btn__content">Reset</span>
|
||||
</button>
|
||||
<button class="btn btn--warn">
|
||||
<span class="btn-ripple"></span>
|
||||
<span class="btn__content">Delete User</span>
|
||||
</button>
|
||||
</div>
|
||||
</v-table-row>
|
||||
<v-table-footer>
|
||||
Dies ist der Footer.
|
||||
</v-table-footer>
|
||||
</v-table>
|
||||
|
||||
<v-table>
|
||||
<v-table-header>
|
||||
<h2>Pages</h2>
|
||||
</v-table-header>
|
||||
<v-table-row>
|
||||
<h3>All Pages</h3>
|
||||
</v-table-row>
|
||||
<v-table-row class="grid12">
|
||||
<div class="col-1to6">Name</div>
|
||||
<div class="col-6to9">Edit, View, Delete</div>
|
||||
<div class="col-9to10">ID</div>
|
||||
<div class="col-11to13">Status</div>
|
||||
</v-table-row>
|
||||
<v-table-row class="grid12">
|
||||
<div class="col-1to6">Sonnenuntergang am Meer</div>
|
||||
<div class="col-6to9 inline">
|
||||
<div class="icon-cont">
|
||||
<div class="icon icon-edit"></div>
|
||||
</div>
|
||||
<div class="icon-cont">
|
||||
<div class="icon icon-visibility"></div>
|
||||
</div>
|
||||
<div class="icon-cont">
|
||||
<div class="icon icon-delete"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-9to10">
|
||||
4
|
||||
</div>
|
||||
<div class="col-11to13">
|
||||
<v-select required name="pageVisibility">
|
||||
<v-label empty="Page Visibility"></v-label>
|
||||
<v-options>
|
||||
<v-option value="visible">Visible</v-option>
|
||||
<v-option value="privat">Privat</v-option>
|
||||
</v-options>
|
||||
</v-select>
|
||||
</div>
|
||||
</v-table-row>
|
||||
<v-table-row class="grid12">
|
||||
<div class="col-1to6">Fernsehen zu Zweit mit Nebenwirkungen</div>
|
||||
<div class="col-6to9 inline">
|
||||
<div class="icon-cont">
|
||||
<div class="icon icon-edit"></div>
|
||||
</div>
|
||||
<div class="icon-cont">
|
||||
<div class="icon icon-visibility"></div>
|
||||
</div>
|
||||
<div class="icon-cont">
|
||||
<div class="icon icon-delete"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-9to10">
|
||||
7
|
||||
</div>
|
||||
<div class="col-11to13">
|
||||
<v-select required name="pageVisibility">
|
||||
<v-label empty="Page Visibility"></v-label>
|
||||
<v-options>
|
||||
<v-option value="visible">Visible</v-option>
|
||||
<v-option value="privat">Privat</v-option>
|
||||
</v-options>
|
||||
</v-select>
|
||||
</div>
|
||||
</v-table-row>
|
||||
<v-table-row class="grid12">
|
||||
<div class="col-1to6">Spiele 1 und 2 und 3</div>
|
||||
<div class="col-6to9 inline">
|
||||
<div class="icon-cont">
|
||||
<div class="icon icon-edit"></div>
|
||||
</div>
|
||||
<div class="icon-cont">
|
||||
<div class="icon icon-visibility"></div>
|
||||
</div>
|
||||
<div class="icon-cont">
|
||||
<div class="icon icon-delete"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-9to10">
|
||||
14
|
||||
</div>
|
||||
<div class="col-11to13">
|
||||
<v-select required name="pageVisibility">
|
||||
<v-label empty="Page Visibility"></v-label>
|
||||
<v-options>
|
||||
<v-option value="visible">Visible</v-option>
|
||||
<v-option value="privat">Privat</v-option>
|
||||
</v-options>
|
||||
</v-select>
|
||||
</div>
|
||||
</v-table-row>
|
||||
<v-table>
|
||||
<div>
|
||||
<h3>Add new Page</h3>
|
||||
<v-button
|
||||
class="input-group"
|
||||
data-label="New Page Name"
|
||||
id="new-page-name"
|
||||
required
|
||||
name="newPageName"
|
||||
data-error="New Page Name is required"></v-button>
|
||||
<button class="btn btn--primary">
|
||||
<span class="btn-ripple"></span>
|
||||
<span class="btn__content">Add Page</span>
|
||||
</button>
|
||||
</div>
|
||||
</v-table>
|
||||
<v-table-footer>
|
||||
Dies ist der Footer.
|
||||
</v-table-footer>
|
||||
</v-table>
|
||||
|
||||
|
||||
<v-table>
|
||||
<v-table-header>
|
||||
<h2>Page: Fernsehen zu Zweit mit Nebenwirkungen</h2>
|
||||
</v-table-header>
|
||||
<v-table-row>
|
||||
<div class="icon-cont">
|
||||
<div class="icon icon-arrow-back"></div>
|
||||
</div>
|
||||
</v-table-row>
|
||||
<v-table-row>
|
||||
<h3>Seitenname:</h3>
|
||||
</v-table-row>
|
||||
<v-table-row>
|
||||
<v-button
|
||||
class="input-group"
|
||||
data-label="New Page Name"
|
||||
id="new-page-name"
|
||||
required
|
||||
name="newPageName"
|
||||
data-error="New Page Name is required"></v-button>
|
||||
</v-table-row>
|
||||
<v-table-row>
|
||||
<v-editor name="pageTextArea" rows="25">!</v-editor>
|
||||
</v-table-row>
|
||||
<v-table-row class="btn-line">
|
||||
<div>
|
||||
<button class="btn btn--valid">
|
||||
<span class="btn-ripple"></span>
|
||||
<span class="btn__content">Save</span>
|
||||
</button>
|
||||
</div>
|
||||
</v-table-row>
|
||||
<v-table-row>
|
||||
<h3>Page Information</h3>
|
||||
</v-table-row>
|
||||
<v-table-row class="grid12">
|
||||
<div class="col-1to4">Author</div>
|
||||
<div class="col-5to6">ID</div>
|
||||
<div class="col-6to11">SEO-URL</div>
|
||||
<div class="col-11to13">Status</div>
|
||||
</v-table-row>
|
||||
<v-table-row class="grid12">
|
||||
<div class="col-1to4">
|
||||
<v-select required name="pageVisibility">
|
||||
<v-label empty="Current Author"></v-label>
|
||||
<v-options>
|
||||
<v-option value="engineertrooper">engineertrooper (Dominic Seela)</v-option>
|
||||
<v-option value="versustunez">versustunez (Maurice Grönwoldt)</v-option>
|
||||
</v-options>
|
||||
</v-select>
|
||||
</div>
|
||||
<div class="col-5to6">
|
||||
7
|
||||
</div>
|
||||
<div class="col-6to11">
|
||||
https://beispiel.seo-url.de/blabla/23455234
|
||||
</div>
|
||||
<div class="col-11to13">
|
||||
<v-select required name="pageVisibility">
|
||||
<v-label empty="Page Visibility"></v-label>
|
||||
<v-options>
|
||||
<v-option value="visible">Visible</v-option>
|
||||
<v-option value="privat">Privat</v-option>
|
||||
</v-options>
|
||||
</v-select>
|
||||
</div>
|
||||
</v-table-row>
|
||||
<v-table-row class="btn-line">
|
||||
<div>
|
||||
<button class="btn btn--valid">
|
||||
<span class="btn-ripple"></span>
|
||||
<span class="btn__content">Save</span>
|
||||
</button>
|
||||
<button class="btn btn--primary">
|
||||
<span class="btn-ripple"></span>
|
||||
<span class="btn__content">Reset</span>
|
||||
</button>
|
||||
<button class="btn btn--warn">
|
||||
<span class="btn-ripple"></span>
|
||||
<span class="btn__content">Delete Page</span>
|
||||
</button>
|
||||
</div>
|
||||
</v-table-row>
|
||||
<v-table-footer>
|
||||
Dies ist der Footer
|
||||
</v-table-footer>
|
||||
</v-table>
|
||||
|
||||
|
||||
<v-table>
|
||||
<v-table-header>
|
||||
<h2>Next header line</h2>
|
||||
</v-table-header>
|
||||
<v-table-row>
|
||||
Hier steht Content
|
||||
</v-table-row>
|
||||
<v-table-footer>
|
||||
Dies ist der Footer
|
||||
</v-table-footer>
|
||||
</v-table>
|
||||
<div class="loader hide">
|
||||
<svg class="spinner" width="65px" height="65px" viewBox="0 0 66 66" xmlns="http://www.w3.org/2000/svg">
|
||||
<circle class="path" fill="none" stroke-width="6" stroke-linecap="round" cx="33" cy="33" r="30"></circle>
|
||||
</svg>
|
||||
</div>
|
||||
</main>
|
4
tpl/admin/jsTemplates/includes/btn.tpl
Normal file
|
@ -0,0 +1,4 @@
|
|||
<button class="btn btn--${type}">
|
||||
<span class="btn-ripple"></span>
|
||||
<span class="btn__content">${content}</span>
|
||||
</button>
|
7
tpl/admin/jsTemplates/includes/input.tpl
Normal file
|
@ -0,0 +1,7 @@
|
|||
<v-input
|
||||
data-label="${label}"
|
||||
required
|
||||
name="${name}"
|
||||
type="${type}"
|
||||
data-error="${error}">
|
||||
</v-input>
|
8
tpl/admin/jsTemplates/includes/select.tpl
Normal file
|
@ -0,0 +1,8 @@
|
|||
<v-select ${required} name="${name}">
|
||||
<v-label empty="${label}"></v-label>
|
||||
<v-options>
|
||||
{foreach(object as item)}
|
||||
<v-option value="${item.value}" ${item.selected}>${item.name}</v-option>
|
||||
{/for}
|
||||
</v-options>
|
||||
</v-select>
|
3
tpl/admin/jsTemplates/includes/svg.tpl
Normal file
|
@ -0,0 +1,3 @@
|
|||
<svg role="img" class="icon">
|
||||
<use href="/theme/admin/icon-sprite.svg#${icon}"></use>
|
||||
</svg>
|
After Width: | Height: | Size: 96 B |
23
tpl/admin/jsTemplates/rolesList.tpl
Normal file
|
@ -0,0 +1,23 @@
|
|||
<v-table>
|
||||
<v-table-header>
|
||||
<h2>Roles</h2>
|
||||
</v-table-header>
|
||||
<v-table-row>
|
||||
<div>
|
||||
<h3>Overview</h3>
|
||||
{foreach(roles as role,key)}
|
||||
<div data-link="/roles" data-id="${role.id}">
|
||||
<span class="icon-text">
|
||||
{include(includes/svg;icon=$role.icon)}
|
||||
</span>
|
||||
<span>${role.name}</span>
|
||||
</div>
|
||||
{/for}
|
||||
</div>
|
||||
<div>
|
||||
<h3>Add new Role</h3>
|
||||
{include(includes/input;label=New Role Name;name=newRoleame;error=New Role Name is required;type=text)}
|
||||
{include(includes/btn;type=primary;content=Add)}
|
||||
</div>
|
||||
</v-table-row>
|
||||
</v-table>
|
|
@ -4,16 +4,21 @@
|
|||
<img class="logo" alt="Be Careful" src="/theme/admin/images/logo.svg">
|
||||
<p class="error-message hide">Login Failed</p>
|
||||
<input type="hidden" name="REDIRECT_TO" value="NO">
|
||||
<div class="input-group">
|
||||
<input id="username" name="USERNAME" required>
|
||||
<label for="username">Username</label>
|
||||
<span class="error">Username is required</span>
|
||||
</div>
|
||||
<div class="input-group">
|
||||
<input id="password" required name="PASSWORD" type="password">
|
||||
<label for="password">Password</label>
|
||||
<span class="error">Password is required</span>
|
||||
</div>
|
||||
<v-input
|
||||
class="input-group"
|
||||
data-label="Username"
|
||||
required
|
||||
name="USERNAME"
|
||||
data-error="Username is required">
|
||||
</v-input>
|
||||
<v-input
|
||||
class="input-group"
|
||||
data-label="Password"
|
||||
required
|
||||
name="PASSWORD"
|
||||
type="password"
|
||||
data-error="Password is required">
|
||||
</v-input>
|
||||
<button class="btn btn--primary">
|
||||
<span class="btn-ripple"></span>
|
||||
<span class="btn__content">Login</span>
|
||||
|
|