VENOM-10: WIP

This commit is contained in:
Maurice Grönwoldt 2020-11-18 17:50:01 +01:00
parent 8d246aa381
commit 5c44d50989
33 changed files with 890 additions and 601 deletions

View file

@ -1,7 +1,9 @@
<?php <?php
//register modules -> need to have the Module Class at parent with the init function ;) //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 // register controllers that can handle templates ;) need to have a render function for this
$controllers = [ $controllers = [

0
logs/.gitkeep Normal file → Executable file
View file

View 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}

File diff suppressed because one or more lines are too long

View 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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View 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'));
})
}
})();

View 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"))});

View file

@ -85,7 +85,9 @@ class VUtils {
}; };
Node.prototype.createNew = function (tag, options) { Node.prototype.createNew = function (tag, options) {
let el = document.createElement(tag); let el = document.createElement(tag);
if (options.classes) {
el.classList.add(...VUtils.get(options.classes, [])); el.classList.add(...VUtils.get(options.classes, []));
}
el.id = VUtils.get(options.id, ''); el.id = VUtils.get(options.id, '');
el.innerHTML = VUtils.get(options.content, ""); el.innerHTML = VUtils.get(options.content, "");
VUtils.mergeKeys(options.dataset, el.dataset); VUtils.mergeKeys(options.dataset, el.dataset);
@ -123,6 +125,7 @@ class VUtils {
}; };
} }
} }
VUtils.makePublic(); VUtils.makePublic();
@ -199,6 +202,7 @@ class VRipple {
} }
} }
} }
const rippler = new VRipple(); const rippler = new VRipple();
@ -426,40 +430,29 @@ class FormHandler {
} }
(function () { (function () {
class VButton extends HTMLElement { class VInput extends HTMLElement {
constructor() { constructor() {
super(); super();
let self = this; let self = this;
self.id = self.id || VUtils.tempId(); self.id = self.id || VUtils.tempId();
self.label = document.createElement('label'); let val = self.innerHTML;
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();
self.innerHTML = ''; self.innerHTML = '';
self.input.required = self.hasAttribute('required'); let input = self.input = self.createNew('input', {id: self.id})
self.input.name = self.getAttribute('name'); let label = self.createNew('label', {content: self.dataset.label});
self.appendChild(self.input) self.createNew('span', {classes: 'error', content: self.dataset.error});
self.appendChild(self.label) label.setAttribute('for', self.id);
self.appendChild(self.errorBox); input.type = self.getAttribute('type') || 'text';
self.input.addMultiListener('change input', self.cb.bind(self)); input.value = val.trim();
input.required = self.hasAttribute('required');
input.name = self.getAttribute('name');
input.addMultiListener('change input', self.cb.bind(self));
} }
connectedCallback() { connectedCallback() {
let cl = this.classList; this.cb({currentTarget: this.input}, true);
if (this.input.value === "") {
cl.remove('focus')
} else {
cl.add('focus')
}
} }
cb(e) { cb(e, noInvalid) {
let el = e.currentTarget let el = e.currentTarget
let errorMessage = $('.error-message', el.find('form')); let errorMessage = $('.error-message', el.find('form'));
if (errorMessage) { if (errorMessage) {
@ -475,13 +468,15 @@ class FormHandler {
cl.add('valid'); cl.add('valid');
cl.remove('invalid'); cl.remove('invalid');
} else { } else {
if (!noInvalid) {
cl.remove('valid'); cl.remove('valid');
cl.add('invalid'); cl.add('invalid');
} }
} }
} }
}
customElements.define("v-button", VButton); customElements.define("v-input", VInput);
if ($('#login')) { if ($('#login')) {
new FormHandler('form#login', 'body', () => { new FormHandler('form#login', 'body', () => {
@ -674,3 +669,517 @@ class FormHandler {
customElements.define("v-editor", VEditor); 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',
])
})();

File diff suppressed because one or more lines are too long

View file

@ -38,6 +38,8 @@ class AdminController implements RenderController
} }
Asset::get()->addCSS('styles', 'style.css', 1); Asset::get()->addCSS('styles', 'style.css', 1);
Asset::get()->addJS('scripts', 'scripts.min.js', 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; return true;
} }

View file

@ -5,6 +5,7 @@ namespace Venom\Admin;
use Venom\Admin\Routes\LoginRoute; use Venom\Admin\Routes\LoginRoute;
use Venom\Admin\Routes\TemplateLoader;
use Venom\Routing\Router; use Venom\Routing\Router;
use Venom\Venom; use Venom\Venom;
@ -12,9 +13,7 @@ class AdminRouterInit
{ {
public static function registerAdminRouters(Venom $venom): void public static function registerAdminRouters(Venom $venom): void
{ {
$router = new Router(Router::ADMIN_ROUTER, 1.0, '/admin/api'); $venom->getRouter(Router::ADMIN_ROUTER)->addRoutes(self::getRoutes());
$router->addRoutes(self::getRoutes());
$venom->addRouter(Router::ADMIN_ROUTER, $router);
} }
public static function getRoutes(): array public static function getRoutes(): array
@ -31,6 +30,15 @@ class AdminRouterInit
"GET" => 'handle' "GET" => 'handle'
] ]
] ]
],
'/templateLoader' => [
'cl' => TemplateLoader::class,
'roles' => ['ROLE_GUEST'],
'routes' => [
'*' => [
"GET" => 'handle'
]
]
] ]
]; ];
} }

View file

@ -21,8 +21,7 @@ class LoginRoute implements Route
{ {
if ($fnc === 'logout') { if ($fnc === 'logout') {
Security::get()->logout(); Security::get()->logout();
$url = ArgumentHandler::get()->getPostItem('REDIRECT_TO', '/admin/'); echo '{"reload": true}';
header('Location: ' . $url);
die(); die();
} }
return true; return true;

View 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();
}
}

View file

@ -4,9 +4,11 @@
namespace Venom\Core; namespace Venom\Core;
use Venom\Venom;
interface Module interface Module
{ {
public function register(): bool; public function register(Venom $venom): bool;
public function init(): void; public function init(): void;
} }

View 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();
}
}

View 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 '';
}
}

View file

@ -27,7 +27,7 @@ class Router
public function addRoutes(array $routes): void public function addRoutes(array $routes): void
{ {
$this->routes = $routes; $this->routes = array_merge($this->routes, $routes);
} }
public function addRoute(string $path, array $route): void public function addRoute(string $path, array $route): void

View file

@ -29,6 +29,7 @@ class Venom
{ {
ExceptionHandler::setExceptionHandler(); ExceptionHandler::setExceptionHandler();
$this->renderer = new VenomRenderer($this); $this->renderer = new VenomRenderer($this);
$this->routers[Router::ADMIN_ROUTER] = new Router(Router::ADMIN_ROUTER, 1.0, '/admin/api');
Asset::get()->setRenderer($this->renderer); Asset::get()->setRenderer($this->renderer);
} }
@ -116,7 +117,7 @@ class Venom
{ {
foreach ($modules as $key => $moduleClass) { foreach ($modules as $key => $moduleClass) {
$module = new $moduleClass; $module = new $moduleClass;
if ($module instanceof Module && $module->register()) { if ($module instanceof Module && $module->register($this)) {
$this->modules[$key] = $module; $this->modules[$key] = $module;
} }
} }
@ -131,4 +132,9 @@ class Venom
{ {
$this->routers[$name] = $router; $this->routers[$name] = $router;
} }
public function getRouter(string $router): ?Router
{
return $this->routers[$router];
}
} }

View file

@ -7,6 +7,7 @@ namespace Venom\Views;
use Venom\Core\ArgumentHandler; use Venom\Core\ArgumentHandler;
use Venom\Core\Config; use Venom\Core\Config;
use Venom\Helper\MetaGenerator; use Venom\Helper\MetaGenerator;
use Venom\Helper\TemplateUtil;
use Venom\Venom; use Venom\Venom;
class VenomRenderer class VenomRenderer
@ -52,8 +53,7 @@ class VenomRenderer
public function renderTemplate($template): void public function renderTemplate($template): void
{ {
// random variable name... to remove it instantly // random variable name... to remove it instantly
echo $this->includeTemplate($template, '1408138186'); echo TemplateUtil::includeTemplate($template);
unset($this->vars['1408138186']);
} }
/** /**
@ -64,17 +64,10 @@ class VenomRenderer
*/ */
public function includeTemplate($template, $varName = '') public function includeTemplate($template, $varName = '')
{ {
$template .= '.php'; $data = TemplateUtil::includeTemplate($template);
if (file_exists($this->templateDir . $template)) {
ob_start();
include_once $this->templateDir . $template;
$data = ob_get_clean();
$this->vars[$varName] = $data; $this->vars[$varName] = $data;
return $data; return $data;
} }
$this->vars[$varName] = '';
return '';
}
public function addVar($name, $value): void public function addVar($name, $value): void
{ {
@ -94,17 +87,12 @@ class VenomRenderer
public function init(?RenderController $controller): void public function init(?RenderController $controller): void
{ {
$this->controller = $controller; $this->controller = $controller;
$data = Config::getInstance()->getRenderer(); if (!Config::getInstance()->isAdmin()) {
$theme = $data->theme;
$base = $data->baseFile ?? 'base';
$this->metaGenerator = new MetaGenerator(); $this->metaGenerator = new MetaGenerator();
if (Config::getInstance()->isAdmin()) {
$base = 'base';
$theme = 'admin';
} else {
$this->metaGenerator->loadById(); $this->metaGenerator->loadById();
} }
$this->baseTemplate = $base . '.php'; $util = TemplateUtil::getInstance();
$this->templateDir = __DIR__ . '/../../../tpl/' . $theme . '/'; $this->templateDir = $util->getDir();
$this->baseTemplate = $util->getBase();
} }
} }

View 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'
]);
}
}

View file

@ -4,531 +4,17 @@
<div data-link="">Meta Data</div> <div data-link="">Meta Data</div>
<div data-link="" class="active">Overview</div> <div data-link="" class="active">Overview</div>
<div data-link="">Pages</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="">SEO-URL</div>
<div data-link="">Users</div> <div data-link="">Users</div>
<div data-link="">Venom-Status</div> <div data-link="">Venom-Status</div>
<div data-link="/admin/api/login/logout">Ausloggen</div> <div data-link="/admin/api/login/logout">Ausloggen</div>
</nav> </nav>
<div class="content-area"> <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>
<div class="icon-cont"> <div class="loader hide">
<div class="icon icon-edit"></div> <svg class="spinner" width="65px" height="65px" viewBox="0 0 66 66" xmlns="http://www.w3.org/2000/svg">
<div class="text">Moderator</div> <circle class="path" fill="none" stroke-width="6" stroke-linecap="round" cx="33" cy="33" r="30"></circle>
</div> </svg>
<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> </div>
</main> </main>

View file

@ -0,0 +1,4 @@
<button class="btn btn--${type}">
<span class="btn-ripple"></span>
<span class="btn__content">${content}</span>
</button>

View file

@ -0,0 +1,7 @@
<v-input
data-label="${label}"
required
name="${name}"
type="${type}"
data-error="${error}">
</v-input>

View 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>

View 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

View 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>

View file

@ -4,16 +4,21 @@
<img class="logo" alt="Be Careful" src="/theme/admin/images/logo.svg"> <img class="logo" alt="Be Careful" src="/theme/admin/images/logo.svg">
<p class="error-message hide">Login Failed</p> <p class="error-message hide">Login Failed</p>
<input type="hidden" name="REDIRECT_TO" value="NO"> <input type="hidden" name="REDIRECT_TO" value="NO">
<div class="input-group"> <v-input
<input id="username" name="USERNAME" required> class="input-group"
<label for="username">Username</label> data-label="Username"
<span class="error">Username is required</span> required
</div> name="USERNAME"
<div class="input-group"> data-error="Username is required">
<input id="password" required name="PASSWORD" type="password"> </v-input>
<label for="password">Password</label> <v-input
<span class="error">Password is required</span> class="input-group"
</div> data-label="Password"
required
name="PASSWORD"
type="password"
data-error="Password is required">
</v-input>
<button class="btn btn--primary"> <button class="btn btn--primary">
<span class="btn-ripple"></span> <span class="btn-ripple"></span>
<span class="btn__content">Login</span> <span class="btn__content">Login</span>