diff --git a/base/module.base.php b/base/module.base.php index e51a2b3..ea74f37 100755 --- a/base/module.base.php +++ b/base/module.base.php @@ -1,7 +1,9 @@ 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 = [ diff --git a/logs/.gitkeep b/logs/.gitkeep old mode 100644 new mode 100755 diff --git a/public/theme/admin/css/admin-panel.css b/public/theme/admin/css/admin-panel.css index 9f98b0f..b2dec83 100644 --- a/public/theme/admin/css/admin-panel.css +++ b/public/theme/admin/css/admin-panel.css @@ -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} \ No newline at end of file +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} \ No newline at end of file diff --git a/public/theme/admin/css/style.css b/public/theme/admin/css/style.css index be88cf8..802a781 100644 --- a/public/theme/admin/css/style.css +++ b/public/theme/admin/css/style.css @@ -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}.btn{border:none;background:#3949ab radial-gradient(circle at 0 0,#3949ab 0,rgba(0,0,0,.2) 100%) no-repeat;color:#fff;padding:7px 1.1rem;margin:10px 0;cursor:pointer;position:relative;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;border-radius:4px;box-shadow:0 2px 5px 0 rgba(0,0,0,.26);overflow:hidden;display:flex;justify-content:center;align-items:center;transition:.5s}.btn--outline{background:0 0;border:1px solid #3949ab}.btn:focus{box-shadow:0 3px 8px 1px rgba(0,0,0,.4)}.btn--valid{background:#39ab48 radial-gradient(circle at 0 0,#39ab48 0,rgba(0,0,0,.2) 100%) no-repeat}.btn--accent{background:#ff0089 radial-gradient(circle at 0 0,#ff0089 0,rgba(0,0,0,.2) 100%) no-repeat}.btn--warn{background:#f87229 radial-gradient(circle at 0 0,#f87229 0,rgba(0,0,0,.2) 100%) no-repeat}.btn-fab{border-radius:2rem;width:2em;height:2em;padding:5px}.btn-ripple{position:absolute;top:0;left:0;width:100%;height:100%;overflow:hidden;background:0 0}.btn-ripple__effect{position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);opacity:1;width:200%;height:0;padding-bottom:200%;border-radius:50%;background:rgba(190,190,190,.3);-webkit-animation:a-ripple .4s ease-in;animation:a-ripple .4s ease-in}.btn-ripple__effect.to-remove{-webkit-animation:remove-ripple .2s linear;animation:remove-ripple .2s linear}.btn--outline .btn-ripple__effect{background:rgba(57,73,171,.5);z-index:-1}.btn__content{z-index:1;font-weight:400;pointer-events:none}@-webkit-keyframes remove-ripple{from{opacity:1}to{opacity:0}}@keyframes remove-ripple{from{opacity:1}to{opacity:0}}@-webkit-keyframes a-ripple{0%{opacity:0;padding-bottom:0;width:0}25%{opacity:1}100%{width:200%;padding-bottom:200%}}@keyframes a-ripple{0%{opacity:0;padding-bottom:0;width:0}25%{opacity:1}100%{width:200%;padding-bottom:200%}}.input-group input{background-color:rgba(0,0,0,.4);border:none;border-bottom:2px solid #3949ab;color:#fff;padding:calc(.75rem - 1px)}.input-group input:focus,.input-group.focus input{border-color:#ff0089}.input-group.valid input{border-color:#39ab48}.input-group.invalid input{border-color:#cf1b1b}.input-group{display:flex;flex-direction:column;margin-bottom:3px;position:relative;padding-top:.7rem}.input-group.no-space{padding-top:.1rem}.input-group label{font-size:.7rem;position:absolute;top:.7rem;left:6px;height:1.5rem;pointer-events:none;vertical-align:middle;transform:translateY(50%);color:#dcdcdc;transition:all .2s ease-out}.input-group input:focus~label,.input-group.focus label{top:0;left:0;transform:translateY(0);font-size:.6rem}.input-group .error{display:none}.input-group.invalid .error{margin-top:2px;display:block;font-size:.7rem;color:#f32f32}.switch{display:flex;flex-direction:row;align-items:center;padding:5px 20px 5px 0}.switch input{position:absolute;-webkit-appearance:none;-moz-appearance:none;appearance:none;opacity:0}.switch label{display:block;border-radius:10px;width:40px;height:20px;background-color:#e9e9e9;position:relative;cursor:pointer;margin-right:.5rem}.switch label:after{content:'';background-color:#c51162;position:absolute;top:2px;left:2px;height:16px;width:16px;border-radius:10px;transition:.5s}.switch input:checked+label:after{transform:translateX(20px);background-color:#007769} \ No newline at end of file +: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}.icon{width:1em;height:1em;vertical-align:middle;font-size:1em;shape-rendering:geometricPrecision;transition:transform .5s cubic-bezier(.22,.61,.36,1);stroke-width:5px;text-align:center;display:block;margin-right:.5rem}.icon-text{display:inline-block}[data-link]{cursor:pointer}.btn{border:none;background:#3949ab radial-gradient(circle at 0 0,#3949ab 0,rgba(0,0,0,.2) 100%) no-repeat;color:#fff;padding:7px 1.1rem;margin:10px 0;cursor:pointer;position:relative;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;border-radius:4px;box-shadow:0 2px 5px 0 rgba(0,0,0,.26);overflow:hidden;display:flex;justify-content:center;align-items:center;transition:.5s}.btn--outline{background:0 0;border:1px solid #3949ab}.btn:focus{box-shadow:0 3px 8px 1px rgba(0,0,0,.4)}.btn--valid{background:#39ab48 radial-gradient(circle at 0 0,#39ab48 0,rgba(0,0,0,.2) 100%) no-repeat}.btn--accent{background:#ff0089 radial-gradient(circle at 0 0,#ff0089 0,rgba(0,0,0,.2) 100%) no-repeat}.btn--warn{background:#f87229 radial-gradient(circle at 0 0,#f87229 0,rgba(0,0,0,.2) 100%) no-repeat}.btn-fab{border-radius:2rem;width:2em;height:2em;padding:5px}.btn-ripple{position:absolute;top:0;left:0;width:100%;height:100%;overflow:hidden;background:0 0}.btn-ripple__effect{position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);opacity:1;width:200%;height:0;padding-bottom:200%;border-radius:50%;background:rgba(190,190,190,.3);-webkit-animation:a-ripple .4s ease-in;animation:a-ripple .4s ease-in}.btn-ripple__effect.to-remove{-webkit-animation:remove-ripple .2s linear;animation:remove-ripple .2s linear}.btn--outline .btn-ripple__effect{background:rgba(57,73,171,.5);z-index:-1}.btn__content{z-index:1;font-weight:400;pointer-events:none}@-webkit-keyframes remove-ripple{from{opacity:1}to{opacity:0}}@keyframes remove-ripple{from{opacity:1}to{opacity:0}}@-webkit-keyframes a-ripple{0%{opacity:0;padding-bottom:0;width:0}25%{opacity:1}100%{width:200%;padding-bottom:200%}}@keyframes a-ripple{0%{opacity:0;padding-bottom:0;width:0}25%{opacity:1}100%{width:200%;padding-bottom:200%}}v-input input{background-color:rgba(0,0,0,.4);border:none;border-bottom:2px solid #3949ab;color:#fff;padding:calc(.75rem - 1px)}v-input input:focus,v-input.focus input{border-color:#ff0089}v-input.valid input{border-color:#39ab48}v-input.invalid input{border-color:#cf1b1b}v-input{display:flex;flex-direction:column;margin-bottom:3px;position:relative;padding-top:.7rem}v-input.no-space{padding-top:.1rem}v-input label{font-size:.7rem;position:absolute;top:.7rem;left:6px;height:1.5rem;pointer-events:none;vertical-align:middle;transform:translateY(50%);color:#dcdcdc;transition:all .2s ease-out}v-input input:focus~label,v-input.focus label{top:0;left:0;transform:translateY(0);font-size:.6rem}v-input .error{display:none}v-input.invalid .error{margin-top:2px;display:block;font-size:.7rem;color:#f32f32}.switch{display:flex;flex-direction:row;align-items:center;padding:5px 20px 5px 0}.switch input{position:absolute;-webkit-appearance:none;-moz-appearance:none;appearance:none;opacity:0}.switch label{display:block;border-radius:10px;width:40px;height:20px;background-color:#e9e9e9;position:relative;cursor:pointer;margin-right:.5rem}.switch label:after{content:'';background-color:#c51162;position:absolute;top:2px;left:2px;height:16px;width:16px;border-radius:10px;transition:.5s}.switch input:checked+label:after{transform:translateX(20px);background-color:#007769}.loader{display:flex;justify-content:center;align-items:center;width:100vw;height:100vh;position:absolute;top:0;z-index:10000;background-color:rgba(0,0,0,.4);opacity:1;transition:opacity .3s}.loader.hide{display:block;opacity:0;pointer-events:none}.loader .spinner{-webkit-animation:rotator 1.4s linear infinite;animation:rotator 1.4s linear infinite}.loader .path{stroke-dasharray:187;stroke-dashoffset:0;transform-origin:center;-webkit-animation:dash 1.4s ease-in-out infinite,colors 5.6s ease-in-out infinite;animation:dash 1.4s ease-in-out infinite,colors 5.6s ease-in-out infinite}@-webkit-keyframes rotator{from{transform:rotate(0)}to{transform:rotate(270deg)}}@keyframes rotator{from{transform:rotate(0)}to{transform:rotate(270deg)}}@-webkit-keyframes colors{0%{stroke:#4285f4}25%{stroke:#de3e35}50%{stroke:#f7c223}75%{stroke:#1b9a59}100%{stroke:#4285f4}}@keyframes colors{0%{stroke:#4285f4}25%{stroke:#de3e35}50%{stroke:#f7c223}75%{stroke:#1b9a59}100%{stroke:#4285f4}}@-webkit-keyframes dash{0%{stroke-dashoffset:187}50%{stroke-dashoffset:46.75;transform:rotate(135deg)}100%{stroke-dashoffset:187;transform:rotate(450deg)}}@keyframes dash{0%{stroke-dashoffset:187}50%{stroke-dashoffset:46.75;transform:rotate(135deg)}100%{stroke-dashoffset:187;transform:rotate(450deg)}}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} \ No newline at end of file diff --git a/public/theme/admin/icon-sprite.svg b/public/theme/admin/icon-sprite.svg new file mode 100644 index 0000000..c013f46 --- /dev/null +++ b/public/theme/admin/icon-sprite.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/theme/admin/images/icon/ic_add_circle_outline_24px.svg b/public/theme/admin/images/icon/ic_add_circle_outline_24px.svg deleted file mode 100644 index 10329ce..0000000 --- a/public/theme/admin/images/icon/ic_add_circle_outline_24px.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/public/theme/admin/images/icon/ic_arrow_back_24px.svg b/public/theme/admin/images/icon/ic_arrow_back_24px.svg deleted file mode 100644 index d153fea..0000000 --- a/public/theme/admin/images/icon/ic_arrow_back_24px.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/public/theme/admin/images/icon/ic_check_box_24px.svg b/public/theme/admin/images/icon/ic_check_box_24px.svg deleted file mode 100644 index 037c140..0000000 --- a/public/theme/admin/images/icon/ic_check_box_24px.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/public/theme/admin/images/icon/ic_delete_24px.svg b/public/theme/admin/images/icon/ic_delete_24px.svg deleted file mode 100644 index e8dde6e..0000000 --- a/public/theme/admin/images/icon/ic_delete_24px.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/public/theme/admin/images/icon/ic_edit_24px.svg b/public/theme/admin/images/icon/ic_edit_24px.svg deleted file mode 100644 index 0991963..0000000 --- a/public/theme/admin/images/icon/ic_edit_24px.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/public/theme/admin/images/icon/ic_visibility_24px.svg b/public/theme/admin/images/icon/ic_visibility_24px.svg deleted file mode 100644 index 737e63c..0000000 --- a/public/theme/admin/images/icon/ic_visibility_24px.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/public/theme/admin/js/components.js b/public/theme/admin/js/components.js new file mode 100644 index 0000000..449a3f1 --- /dev/null +++ b/public/theme/admin/js/components.js @@ -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')); + }) + } +})(); \ No newline at end of file diff --git a/public/theme/admin/js/components.min.js b/public/theme/admin/js/components.min.js new file mode 100644 index 0000000..aeeb4bb --- /dev/null +++ b/public/theme/admin/js/components.min.js @@ -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"))}); \ No newline at end of file diff --git a/public/theme/admin/js/scripts.js b/public/theme/admin/js/scripts.js index 51bbcd9..50a7c29 100644 --- a/public/theme/admin/js/scripts.js +++ b/public/theme/admin/js/scripts.js @@ -85,7 +85,9 @@ class VUtils { }; Node.prototype.createNew = function (tag, options) { let el = document.createElement(tag); - el.classList.add(...VUtils.get(options.classes, [])); + 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 { - cl.remove('valid'); - cl.add('invalid'); + 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', () => { @@ -673,4 +668,518 @@ 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('', 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', + ]) })(); \ No newline at end of file diff --git a/public/theme/admin/js/scripts.min.js b/public/theme/admin/js/scripts.min.js index 4b1286a..db8f724 100644 --- a/public/theme/admin/js/scripts.min.js +++ b/public/theme/admin/js/scripts.min.js @@ -1 +1 @@ -class VUtils{static makePublic(){VUtils.isUsed||(this.initHandlers(),VUtils.isUsed=!0,console.log("[VUtils] is now available in the Global Space! no VUtils. anymore needed"))}static initHandlers(){window.$=this.$,window.$$=this.$$,window.tryCatch=this.tryCatch,VUtils.nodePrototypes()}static $(e,t){return(t=t||document).querySelector(e)}static $$(e,t){return(t=t||document).querySelectorAll(e)}static tryCatch(e,t,i){e=VUtils.wrap(e,[]),i=i||console.error,t=t||console.log;try{t(...e)}catch(e){i(e)}}static forEach(e,t,i){for(let s=0;s{let n=e.target;if(e.detail instanceof HTMLElement&&(n=e.detail),n instanceof HTMLElement)if(n.matches(t))VUtils.tryCatch([e,n],i,s);else{const r=n.find(t);r&&VUtils.tryCatch([e,r],i,s)}})},Node.prototype.addMultiListener=function(e,t,i={}){let s=e.split(" ");for(let e of s)this.addEventListener(e,t,i)}}}VUtils.makePublic();class VRipple{constructor(e={}){if(!VUtils.isUsed)throw"VRipply is only with Public VUtils usable!";let t=this;if(t.options=JSON.parse('{"classes":["btn-ripple__effect"],"target":"body","selector":".btn-ripple"}'),VUtils.mergeOptions(t.options,e),t.options.selector.indexOf("#")>-1)throw"ID's are not allowed as selector!";this.instanceCheck(),this.ripples=[],requestAnimationFrame(this.initHandler.bind(this))}instanceCheck(){let e=this.options;const t=[e.target,e.selector,e.classes.join(".")].join(" ");VRipple.instances=VRipple.instances||{},VRipple.instances[t]=this}initHandler(){let e=this,t=e.options.selector;$(e.options.target).addDelegatedEventListener("mousedown touchstart",t,(t,i)=>{let s=t.touches?t.touches[0]:t,n=i.parentNode,r=i.createNew("span",e.options),o=n.getBoundingClientRect(),l=s.clientX-o.left,a=s.clientY-o.top;r.style.top=a+"px",r.style.left=l+"px",r._mouseDown=!0,r._animationEnded=!1,e.ripples.push(r)}),document.body.addDelegatedEventListener("animationend","."+VUtils.get(e.options.classes,""),e.rippleEnd.bind(e)),document.body._vRippleInit||(document.body.addMultiListener("mouseup touchend mouseleave rippleClose",t=>{let i=Object.keys(VRipple.instances);for(let s of i)for(let i of VRipple.instances[s].ripples)e.rippleEnd.bind(VRipple.instances[s])(t,i)}),document.body._vRippleInit=!0)}rippleEnd(e,t){t.parentNode&&("animationend"===e.type?t._animationEnded=!0:t._mouseDown=!1,!t._mouseDown&&t._animationEnded&&(t.classList.contains("to-remove")?(t.parentNode.removeChild(t),this.ripples.splice(this.ripples.indexOf(t),1)):t.classList.add("to-remove")))}}const rippler=new VRipple;!function(){window._openVSelect=null,requestAnimationFrame(e=>{document.body.addEventListener("click",e=>{window._openVSelect&&e.target.closest("v-select")!==window._openVSelect&&window._openVSelect.toggle(!1)})});class e extends HTMLElement{constructor(){super();let e=this;e._in=this.attachInternals(),e._in.role="select",e.setAttribute("tabindex",0),e.update()}static get formAssociated(){return!0}static get observedAttributes(){return["required","validity"]}get required(){return this.hasAttribute("required")}set required(e){this.toggleAttribute("required",Boolean(e))}get name(){return this.getAttribute("name")}set name(e){this.toggleAttribute("name",e)}get form(){return this._in.form}get options(){return $$("v-options v-option",this)}get selected(){return $$("v-options v-option[selected]",this)}update(){let e=[],t=$("v-label",this),i=new FormData;this.selected.forEach(t=>{e.push(t.innerText),i.append(this.name,t.value)}),t.attributeChangedCallback("value","",e.join(", ")),this.required&&0===e.length?this._in.setValidity({customError:!0},"Option is needed"):this._in.setValidity({}),this._in.setFormValue(i)}checkValidity(){return this._in.checkValidity()}reportValidity(){return this._in.reportValidity()}toggle(e){window._openVSelect&&e&&window._openVSelect.toggleSelect(!1);const t=$("v-options",this);if(!e||this.isOpen)t.style.maxHeight="0",window._openVSelect=!1,this.isOpen=!1,this.update();else{t.focus();let e=0,i=t.children;for(let t=0;t{let t=this.parentNode.parentNode,i=!this.selected;if(!t.hasAttribute("multiple")){t.toggle(!1);for(let e of t.selected)e!==this&&e.removeAttribute("selected")}this.disabled||(this.attributeChangedCallback("selected",!1,i,!0),this.parentNode.parentNode.update())})}static get observedAttributes(){return["selected","disabled","value"]}attributeChangedCallback(e,t,i,s){"selected"===e&&this.hasAttribute("disabled")?this.removeAttribute(e):("disabled"===e&&!0===i&&this.hasAttribute("selected")&&this.attributeChangedCallback("selected",!1,!1),s&&(i?this.setAttribute(e,i):this.removeAttribute(e)),this[e]=i)}}class i extends HTMLElement{constructor(){super(),this.empty=this.getAttribute("empty")||"",this.innerHTML=this.getAttribute("value")||this.empty,this.addEventListener("click",this.openPopUp.bind(this))}static get observedAttributes(){return["empty","value"]}openPopUp(){this.parentNode.toggle(!0)}attributeChangedCallback(e,t,i){"value"===e&&(this.innerHTML=i||this.empty),this[e]=i}}customElements.define("v-label",i),customElements.define("v-option",t),customElements.define("v-select",e)}();class FormHandler{constructor(e,t,i,s){this.cb=i||console.log,this.err=s||console.err,$(t).addDelegatedEventListener("submit",e,this.handleEvent.bind(this))}handleEvent(e,t){if(e.preventDefault(),t.checkValidity()){if(""===(t.action??""))return void console.error("No URL Found on Form",t);fetch(t.action,{method:t.method.toUpperCase(),credentials:"same-origin",body:new FormData(t),redirect:"manual"}).then(e=>{if(!e.ok)throw new Error("Network response errored");return e.json()}).then(e=>this.cb(e,t)).catch(e=>this.err(e,t))}else VUtils.forEach($$("input",t),e=>{if(!e.checkValidity()){let t=e.parentNode;t.classList.remove("valid"),t.classList.add("invalid")}})}}!function(){class e extends HTMLElement{constructor(){super();let e=this;e.id=e.id||VUtils.tempId(),e.label=document.createElement("label"),e.input=document.createElement("input"),e.errorBox=document.createElement("span"),e.errorBox.classList.add("error"),e.errorBox.innerHTML=e.dataset.error,e.label.setAttribute("for",e.id),e.input.id=e.id,e.input.type=e.getAttribute("type")||"text",e.label.innerHTML=e.dataset.label,e.input.value=e.innerHTML.trim(),e.innerHTML="",e.input.required=e.hasAttribute("required"),e.input.name=e.getAttribute("name"),e.appendChild(e.input),e.appendChild(e.label),e.appendChild(e.errorBox),e.input.addMultiListener("change input",e.cb.bind(e))}connectedCallback(){let e=this.classList;""===this.input.value?e.remove("focus"):e.add("focus")}cb(e){let t=e.currentTarget,i=$(".error-message",t.find("form"));i&&i.classList.add("hide");let s=this.classList;""===t.value?s.remove("focus"):s.add("focus"),t.checkValidity()?(s.add("valid"),s.remove("invalid")):(s.remove("valid"),s.add("invalid"))}}customElements.define("v-button",e),$("#login")&&new FormHandler("form#login","body",()=>{location.reload()},(e,t)=>{$(".error-message",t).classList.remove("hide")})}(),(()=>{class e{constructor(e){let t=this;t.editor=e instanceof HTMLElement?e:$(e),t.todoOnKey={},t.keys=[],t.backup=[],t.taberr=[">"," ","\n","<"],t.name="veditor-"+t.editor.id,t.init(),t.selfClosing=["img","area","base","br","col","embed","hr","img","input","link","menuitem","meta","param","source","track"],t.restore()}init(){let e=this;e.editor.addEventListener("keydown",e.handleKey.bind(e)),e.addKey("Tab",e.pressTab.bind(e)),e.addKey("<",e.addEmptyTags.bind(e)),e.addKey("ctrl-z",e.undo.bind(e)),e.addKey("ctrl-s",e.store.bind(e)),e.addKey("ctrl-shift-S",e.delete.bind(e)),e.editor.classList.add(e.name,"veditor")}registerSelfClosing(e){this.selfClosing.push(e)}restore(){let e=localStorage.getItem(this.name);e&&(this.editor.value=e)}delete(){localStorage.removeItem(this.name),console.log(`[VEdit] Editor: ${this.name} removed`)}store(){localStorage.setItem(this.name,this.editor.value),console.log(`[VEdit] Editor: ${this.name} saved`)}handleKey(e){let t,i=this;if(e.ctrlKey&&"Control"===e.key||e.shiftKey&&"Shift"===e.key)return;if(e.ctrlKey&&e.shiftKey?t="ctrl-shift-"+e.key:e.ctrlKey&&(t="ctrl-"+e.key),t&&-1!==this.keys.indexOf(t))return e.preventDefault(),void this.todoOnKey[t]();let s=i.editor.value;const n=i.editor.selectionStart;if(i.backup.length>50&&i.backup.shift(),i.backup.push([s,n]),i.keys.indexOf(e.key)>-1){e.preventDefault();let t=i.todoOnKey[e.key](n,s,this.editor);t[0].push(s.substr(n)),i.afterWork(t)}}undo(){let e=this.backup.pop()||[this.editor.value,this.editor.selectionStart];this.editor.value=e[0],this.editor.setSelectionRange(e[1],e[1])}afterWork(e){this.setText(e[0].join("")),this.editor.setSelectionRange(e[1],e[1])}setText(e){this.editor.value=e}addKey(e,t){this.todoOnKey[e]=t,this.keys.push(e)}addEmptyTags(e,t,i){return[[t.substr(0,e),"<>"],e+1]}pressTab(e,t,i){let s,n,r=this,o=e;if(0===e||-1!==r.taberr.indexOf(t[e-1]))s=" ",o+=4,n=t.substr(0,e);else if(-1===r.taberr.indexOf(t[e-1])){let i=2;for(;-1===r.taberr.indexOf(t[e-i])&&e-i>0;)i++;e-i>0&&(i-=1);let l=r.generateTag(t.substr(e-i,i).trim());s=l[0],o=e-i+l[1],n=t.substr(0,e-i)}return[[n,s],o]}generateTag(e){let t,i={".":[],"#":[]},s=Object.keys(i),n="cl",r=e.split(/([#.])/g);t=r.shift();for(let e of r)s.indexOf(e)>-1?n=e:i[n].push(e);let o="";i["."].length>0&&(o+=` class="${i["."].join(" ")}"`),i["#"].length>0&&(o+=` id="${i["#"].join("-")}"`);let l="";-1===this.selfClosing.indexOf(t.trim())&&(l=``);let a=`<${t}${o}>`;return[`${a}${l}`,a.length]}}class t extends HTMLElement{constructor(){super(),this.editor=document.createElement("textarea"),this.editor.innerHTML=this.innerHTML,this.editor.id=this.getAttribute("name");for(let e of this.attributes)this.editor.setAttribute(e.name,e.value);this.innerHTML="",this.appendChild(this.editor),this.edit=new e(this.editor)}connectedCallback(){this.edit.restore()}disconnectedCallback(){this.edit.save()}}customElements.define("v-editor",t)})(); \ No newline at end of file +class VUtils{static makePublic(){VUtils.isUsed||(this.initHandlers(),VUtils.isUsed=!0,console.log("[VUtils] is now available in the Global Space! no VUtils. anymore needed"))}static initHandlers(){window.$=this.$,window.$$=this.$$,window.tryCatch=this.tryCatch,VUtils.nodePrototypes()}static $(e,t){return(t=t||document).querySelector(e)}static $$(e,t){return(t=t||document).querySelectorAll(e)}static tryCatch(e,t,i){e=VUtils.wrap(e,[]),i=i||console.error,t=t||console.log;try{t(...e)}catch(e){i(e)}}static forEach(e,t,i){for(let s=0;s{let n=e.target;if(e.detail instanceof HTMLElement&&(n=e.detail),n instanceof HTMLElement)if(n.matches(t))VUtils.tryCatch([e,n],i,s);else{const r=n.find(t);r&&VUtils.tryCatch([e,r],i,s)}})},Node.prototype.addMultiListener=function(e,t,i={}){let s=e.split(" ");for(let e of s)this.addEventListener(e,t,i)}}}VUtils.makePublic();class VRipple{constructor(e={}){if(!VUtils.isUsed)throw"VRipply is only with Public VUtils usable!";let t=this;if(t.options=JSON.parse('{"classes":["btn-ripple__effect"],"target":"body","selector":".btn-ripple"}'),VUtils.mergeOptions(t.options,e),t.options.selector.indexOf("#")>-1)throw"ID's are not allowed as selector!";this.instanceCheck(),this.ripples=[],requestAnimationFrame(this.initHandler.bind(this))}instanceCheck(){let e=this.options;const t=[e.target,e.selector,e.classes.join(".")].join(" ");VRipple.instances=VRipple.instances||{},VRipple.instances[t]=this}initHandler(){let e=this,t=e.options.selector;$(e.options.target).addDelegatedEventListener("mousedown touchstart",t,(t,i)=>{let s=t.touches?t.touches[0]:t,n=i.parentNode,r=i.createNew("span",e.options),a=n.getBoundingClientRect(),o=s.clientX-a.left,l=s.clientY-a.top;r.style.top=l+"px",r.style.left=o+"px",r._mouseDown=!0,r._animationEnded=!1,e.ripples.push(r)}),document.body.addDelegatedEventListener("animationend","."+VUtils.get(e.options.classes,""),e.rippleEnd.bind(e)),document.body._vRippleInit||(document.body.addMultiListener("mouseup touchend mouseleave rippleClose",t=>{let i=Object.keys(VRipple.instances);for(let s of i)for(let i of VRipple.instances[s].ripples)e.rippleEnd.bind(VRipple.instances[s])(t,i)}),document.body._vRippleInit=!0)}rippleEnd(e,t){t.parentNode&&("animationend"===e.type?t._animationEnded=!0:t._mouseDown=!1,!t._mouseDown&&t._animationEnded&&(t.classList.contains("to-remove")?(t.parentNode.removeChild(t),this.ripples.splice(this.ripples.indexOf(t),1)):t.classList.add("to-remove")))}}const rippler=new VRipple;!function(){window._openVSelect=null,requestAnimationFrame(e=>{document.body.addEventListener("click",e=>{window._openVSelect&&e.target.closest("v-select")!==window._openVSelect&&window._openVSelect.toggle(!1)})});class e extends HTMLElement{constructor(){super();let e=this;e._in=this.attachInternals(),e._in.role="select",e.setAttribute("tabindex",0),e.update()}static get formAssociated(){return!0}static get observedAttributes(){return["required","validity"]}get required(){return this.hasAttribute("required")}set required(e){this.toggleAttribute("required",Boolean(e))}get name(){return this.getAttribute("name")}set name(e){this.toggleAttribute("name",e)}get form(){return this._in.form}get options(){return $$("v-options v-option",this)}get selected(){return $$("v-options v-option[selected]",this)}update(){let e=[],t=$("v-label",this),i=new FormData;this.selected.forEach(t=>{e.push(t.innerText),i.append(this.name,t.value)}),t.attributeChangedCallback("value","",e.join(", ")),this.required&&0===e.length?this._in.setValidity({customError:!0},"Option is needed"):this._in.setValidity({}),this._in.setFormValue(i)}checkValidity(){return this._in.checkValidity()}reportValidity(){return this._in.reportValidity()}toggle(e){window._openVSelect&&e&&window._openVSelect.toggleSelect(!1);const t=$("v-options",this);if(!e||this.isOpen)t.style.maxHeight="0",window._openVSelect=!1,this.isOpen=!1,this.update();else{t.focus();let e=0,i=t.children;for(let t=0;t{let t=this.parentNode.parentNode,i=!this.selected;if(!t.hasAttribute("multiple")){t.toggle(!1);for(let e of t.selected)e!==this&&e.removeAttribute("selected")}this.disabled||(this.attributeChangedCallback("selected",!1,i,!0),this.parentNode.parentNode.update())})}static get observedAttributes(){return["selected","disabled","value"]}attributeChangedCallback(e,t,i,s){"selected"===e&&this.hasAttribute("disabled")?this.removeAttribute(e):("disabled"===e&&!0===i&&this.hasAttribute("selected")&&this.attributeChangedCallback("selected",!1,!1),s&&(i?this.setAttribute(e,i):this.removeAttribute(e)),this[e]=i)}}class i extends HTMLElement{constructor(){super(),this.empty=this.getAttribute("empty")||"",this.innerHTML=this.getAttribute("value")||this.empty,this.addEventListener("click",this.openPopUp.bind(this))}static get observedAttributes(){return["empty","value"]}openPopUp(){this.parentNode.toggle(!0)}attributeChangedCallback(e,t,i){"value"===e&&(this.innerHTML=i||this.empty),this[e]=i}}customElements.define("v-label",i),customElements.define("v-option",t),customElements.define("v-select",e)}();class FormHandler{constructor(e,t,i,s){this.cb=i||console.log,this.err=s||console.err,$(t).addDelegatedEventListener("submit",e,this.handleEvent.bind(this))}handleEvent(e,t){if(e.preventDefault(),t.checkValidity()){if(""===(t.action??""))return void console.error("No URL Found on Form",t);fetch(t.action,{method:t.method.toUpperCase(),credentials:"same-origin",body:new FormData(t),redirect:"manual"}).then(e=>{if(!e.ok)throw new Error("Network response errored");return e.json()}).then(e=>this.cb(e,t)).catch(e=>this.err(e,t))}else VUtils.forEach($$("input",t),e=>{if(!e.checkValidity()){let t=e.parentNode;t.classList.remove("valid"),t.classList.add("invalid")}})}}!function(){class e extends HTMLElement{constructor(){super();let e=this;e.id=e.id||VUtils.tempId();let t=e.innerHTML;e.innerHTML="";let i=e.input=e.createNew("input",{id:e.id}),s=e.createNew("label",{content:e.dataset.label});e.createNew("span",{classes:"error",content:e.dataset.error}),s.setAttribute("for",e.id),i.type=e.getAttribute("type")||"text",i.value=t.trim(),i.required=e.hasAttribute("required"),i.name=e.getAttribute("name"),i.addMultiListener("change input",e.cb.bind(e))}connectedCallback(){this.cb({currentTarget:this.input},!0)}cb(e,t){let i=e.currentTarget,s=$(".error-message",i.find("form"));s&&s.classList.add("hide");let n=this.classList;""===i.value?n.remove("focus"):n.add("focus"),i.checkValidity()?(n.add("valid"),n.remove("invalid")):t||(n.remove("valid"),n.add("invalid"))}}customElements.define("v-input",e),$("#login")&&new FormHandler("form#login","body",()=>{location.reload()},(e,t)=>{$(".error-message",t).classList.remove("hide")})}(),(()=>{class e{constructor(e){let t=this;t.editor=e instanceof HTMLElement?e:$(e),t.todoOnKey={},t.keys=[],t.backup=[],t.taberr=[">"," ","\n","<"],t.name="veditor-"+t.editor.id,t.init(),t.selfClosing=["img","area","base","br","col","embed","hr","img","input","link","menuitem","meta","param","source","track"],t.restore()}init(){let e=this;e.editor.addEventListener("keydown",e.handleKey.bind(e)),e.addKey("Tab",e.pressTab.bind(e)),e.addKey("<",e.addEmptyTags.bind(e)),e.addKey("ctrl-z",e.undo.bind(e)),e.addKey("ctrl-s",e.store.bind(e)),e.addKey("ctrl-shift-S",e.delete.bind(e)),e.editor.classList.add(e.name,"veditor")}registerSelfClosing(e){this.selfClosing.push(e)}restore(){let e=localStorage.getItem(this.name);e&&(this.editor.value=e)}delete(){localStorage.removeItem(this.name),console.log(`[VEdit] Editor: ${this.name} removed`)}store(){localStorage.setItem(this.name,this.editor.value),console.log(`[VEdit] Editor: ${this.name} saved`)}handleKey(e){let t,i=this;if(e.ctrlKey&&"Control"===e.key||e.shiftKey&&"Shift"===e.key)return;if(e.ctrlKey&&e.shiftKey?t="ctrl-shift-"+e.key:e.ctrlKey&&(t="ctrl-"+e.key),t&&-1!==this.keys.indexOf(t))return e.preventDefault(),void this.todoOnKey[t]();let s=i.editor.value;const n=i.editor.selectionStart;if(i.backup.length>50&&i.backup.shift(),i.backup.push([s,n]),i.keys.indexOf(e.key)>-1){e.preventDefault();let t=i.todoOnKey[e.key](n,s,this.editor);t[0].push(s.substr(n)),i.afterWork(t)}}undo(){let e=this.backup.pop()||[this.editor.value,this.editor.selectionStart];this.editor.value=e[0],this.editor.setSelectionRange(e[1],e[1])}afterWork(e){this.setText(e[0].join("")),this.editor.setSelectionRange(e[1],e[1])}setText(e){this.editor.value=e}addKey(e,t){this.todoOnKey[e]=t,this.keys.push(e)}addEmptyTags(e,t,i){return[[t.substr(0,e),"<>"],e+1]}pressTab(e,t,i){let s,n,r=this,a=e;if(0===e||-1!==r.taberr.indexOf(t[e-1]))s=" ",a+=4,n=t.substr(0,e);else if(-1===r.taberr.indexOf(t[e-1])){let i=2;for(;-1===r.taberr.indexOf(t[e-i])&&e-i>0;)i++;e-i>0&&(i-=1);let o=r.generateTag(t.substr(e-i,i).trim());s=o[0],a=e-i+o[1],n=t.substr(0,e-i)}return[[n,s],a]}generateTag(e){let t,i={".":[],"#":[]},s=Object.keys(i),n="cl",r=e.split(/([#.])/g);t=r.shift();for(let e of r)s.indexOf(e)>-1?n=e:i[n].push(e);let a="";i["."].length>0&&(a+=` class="${i["."].join(" ")}"`),i["#"].length>0&&(a+=` id="${i["#"].join("-")}"`);let o="";-1===this.selfClosing.indexOf(t.trim())&&(o=``);let l=`<${t}${a}>`;return[`${l}${o}`,l.length]}}class t extends HTMLElement{constructor(){super(),this.editor=document.createElement("textarea"),this.editor.innerHTML=this.innerHTML,this.editor.id=this.getAttribute("name");for(let e of this.attributes)this.editor.setAttribute(e.name,e.value);this.innerHTML="",this.appendChild(this.editor),this.edit=new e(this.editor)}connectedCallback(){this.edit.restore()}disconnectedCallback(){this.edit.save()}}customElements.define("v-editor",t)})(),window.router=new class{constructor(e){this.options=e,document.body.addDelegatedEventListener("click","[data-link]",(e,t)=>{e.preventDefault(),$$("[data-link].active").forEach(e=>e.classList.remove("active"));let i=$(".loader").classList;i.remove("hide"),this.handleRouting(t.dataset).then(e=>{i.add("hide"),t.classList.add("active")})}),document.body.addEventListener("triggerRouter",e=>{this.handle(sessionStorage.getItem("url"))}),window.addEventListener("popstate",e=>{this.handle(e.state)}),this.components={},window.dispatchEvent(new CustomEvent("routerReady")),window.routerIsReady=!0}handle(e){e&&(e=JSON.parse(e),this.handleRouting(e.data).then(t=>{let i=$('[data-link="'+e.data.link+'"]');i&&i.classList.add("active")}))}async handleRouting(e){try{let t=e.link,i=this.components[t];if(""===t)return null;i&&(t=i.getUrl(e));let s=await this.handleRequest(t,!0);return s.reload?location.reload():(i=i||this.components[s.component]||null,i?(sessionStorage.setItem("url",JSON.stringify({data:e})),i.handle(s,e).then(t=>{$(this.options.toReplace).innerHTML=t,history.pushState(JSON.stringify({data:e}),document.title)})):await alert("Error"),null)}catch(e){return e}}async handleRequest(e,t){if(""!==(e=e.trim()))return await fetch(e,{credentials:"same-origin"}).then(e=>{if(!e.ok)throw"URL is Status: "+e.status;let i=e.headers.get("Content-Type");return-1!==i.indexOf("json")||t?e.json():-1!==i.indexOf("text")?e.text():e.blob()}).catch(e=>(console.error(e),null))}addComponent(e,t){this.components[e]=t}}({toReplace:".content-area"});class VTpeLCore{constructor(e={}){this.templates={},this.dir=e.path||"/tpl/",this.suffix=e.suffix||"tpl",this.path=e.template||`${this.dir}%s.${this.suffix}`,this.cache=void 0===e.cache||e.cache}async loadTemplate(e){if(this.templates[e])return null;let t=this.path.replace("%s",e),i=await fetch(t,{cache:"force-cache"});if(i.ok){let t=await i.text();this.addTpl(e,t)}return null}async loadArray(e){for(let t of e)await this.loadTemplate(t)}addTpl(e,t){(this.templates[e]=new VTpeLTemplate(e,t,this)).parseContent(this.cache)}async renderOn(e,t){return this.templates[e]?await this.templates[e].render(t):""}}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(e,t){let i=this;i.name=e,i.legex=t.trim(),i.index=0,i.content="",i.parsed=[],i.contexts=[0]}tokenize(){let e=this;for(e.index=0;e.index0&&(this.index+=s),s>0||-1===s}nextContainsRaw(e,t,i){"string"==typeof t&&(t=t.split(""));let s=t.length;if(s<1)return-1;for(let n=0;n ${o}] >> ${s}`),i.index=o+e.length,i.content=s.trim(),void i.addType(t);s+=l}if("\n"===a)return i.index=r.length,i.content=s.trim(),void i.addType(t);throw"Template variable at Position: "+n+" not closed!"}getOperator(e){let t=[];for(let i=0;iaddCSS('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; } diff --git a/src/Venom/Admin/AdminRouterInit.php b/src/Venom/Admin/AdminRouterInit.php index e1643ab..7a8ce90 100644 --- a/src/Venom/Admin/AdminRouterInit.php +++ b/src/Venom/Admin/AdminRouterInit.php @@ -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' + ] + ] ] ]; } diff --git a/src/Venom/Admin/Routes/LoginRoute.php b/src/Venom/Admin/Routes/LoginRoute.php index d33680f..ef4c9ce 100644 --- a/src/Venom/Admin/Routes/LoginRoute.php +++ b/src/Venom/Admin/Routes/LoginRoute.php @@ -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; diff --git a/src/Venom/Admin/Routes/TemplateLoader.php b/src/Venom/Admin/Routes/TemplateLoader.php new file mode 100644 index 0000000..b205778 --- /dev/null +++ b/src/Venom/Admin/Routes/TemplateLoader.php @@ -0,0 +1,23 @@ +getItem('tpl', '..'); + if (strpos($id, '..')) { + return false; + } + echo TemplateUtil::includeTemplate('jsTemplates/' . $id, '.tpl'); + die(); + } +} \ No newline at end of file diff --git a/src/Venom/Core/Module.php b/src/Venom/Core/Module.php index 746d386..ea44204 100644 --- a/src/Venom/Core/Module.php +++ b/src/Venom/Core/Module.php @@ -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; } \ No newline at end of file diff --git a/src/Venom/Helper/AdminHelper.php b/src/Venom/Helper/AdminHelper.php new file mode 100644 index 0000000..65152a6 --- /dev/null +++ b/src/Venom/Helper/AdminHelper.php @@ -0,0 +1,25 @@ + $content, + 'component' => $component + ]; + + if ($shouldReload) { + $response['reload'] = true; + } + if ($extra) { + $response['extra'] = $extra; + } + echo json_encode($response); + die(); + } +} \ No newline at end of file diff --git a/src/Venom/Helper/TemplateUtil.php b/src/Venom/Helper/TemplateUtil.php new file mode 100644 index 0000000..9bfcb07 --- /dev/null +++ b/src/Venom/Helper/TemplateUtil.php @@ -0,0 +1,58 @@ +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 ''; + } +} \ No newline at end of file diff --git a/src/Venom/Routing/Router.php b/src/Venom/Routing/Router.php index af980ea..96e3881 100644 --- a/src/Venom/Routing/Router.php +++ b/src/Venom/Routing/Router.php @@ -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 diff --git a/src/Venom/Venom.php b/src/Venom/Venom.php index 9dfaa06..1c1421d 100755 --- a/src/Venom/Venom.php +++ b/src/Venom/Venom.php @@ -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]; + } } \ No newline at end of file diff --git a/src/Venom/Views/VenomRenderer.php b/src/Venom/Views/VenomRenderer.php index 3dfb403..97cedc5 100755 --- a/src/Venom/Views/VenomRenderer.php +++ b/src/Venom/Views/VenomRenderer.php @@ -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,16 +64,9 @@ 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(); - $this->vars[$varName] = $data; - return $data; - } - $this->vars[$varName] = ''; - return ''; + $data = TemplateUtil::includeTemplate($template); + $this->vars[$varName] = $data; + return $data; } 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'; - $this->metaGenerator = new MetaGenerator(); - if (Config::getInstance()->isAdmin()) { - $base = 'base'; - $theme = 'admin'; - } else { + if (!Config::getInstance()->isAdmin()) { + $this->metaGenerator = new MetaGenerator(); $this->metaGenerator->loadById(); } - $this->baseTemplate = $base . '.php'; - $this->templateDir = __DIR__ . '/../../../tpl/' . $theme . '/'; + $util = TemplateUtil::getInstance(); + $this->templateDir = $util->getDir(); + $this->baseTemplate = $util->getBase(); } } diff --git a/src/modules/RoleModule.php b/src/modules/RoleModule.php new file mode 100644 index 0000000..7740e69 --- /dev/null +++ b/src/modules/RoleModule.php @@ -0,0 +1,79 @@ +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' + ]); + } +} \ No newline at end of file diff --git a/tpl/admin/admin-panel.php b/tpl/admin/admin-panel.php index 4b4b03e..9cbc052 100644 --- a/tpl/admin/admin-panel.php +++ b/tpl/admin/admin-panel.php @@ -4,531 +4,17 @@
Meta Data
Overview
Pages
-
Roles
+
Roles
SEO-URL
Users
Venom-Status
Ausloggen
- - -

Roles

-
- -
-

Overview

-
-
-
Admin (secured)
-
-
-
-
Moderator
-
-
-
-
Predator
-
-
-
-

Add new Role

- - -
-
- - Dies ist der Footer. - -
- - - - -

Role: Admin

-
- -
-
-
-
- -
-

Role Status

-
- - - If enabled role is active. -
-
-
-

Role Name

- -
-
- -
-

Module

-
Meta-Data
-
Pages
-
Roles
-
SEO-URL
-
Users
-
VENOM-Status
-
-
-

Edit

-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
-
-

View

-
- - - -
- -
- - - -
- -
- - - -
- -
- - - -
- -
- - - -
- -
- - - -
-
-
- -
- - - -
-
- - Dies ist der Footer. - -
- - - - -

Users

-
- -
-

All Users

-
-
-
ALLE Alphabetisch ordnen!
-
-
-
-
derflieger (Ina Ruh)
-
-
-
-
engineertrooper (Dominic Seela)
-
-
-
-
versustunez (Maurice Grönwoldt)
-
-
-
-
vollglaswasser (Marie Joseph)
-
-
-
-

Add new User

-
- -
- -
-
- - Dies ist der Footer. - -
- - - - -

User: engineertrooper

-
- -
-
-
-
- -
- EngineerTrooper - - Dominic Seela - -
-
- kontakt@engineertrooper.com - -
-
- kontakt@engineertrooper.com - -
New Password:
-
- - - New Password is required -
-
New Password repeat:
-
- - - New Password Repeat is required -
-
-
- -
- - - -
-
- - Dies ist der Footer. - -
- - - -

Pages

-
- -

All Pages

-
- -
Name
-
Edit, View, Delete
-
ID
-
Status
-
- -
Sonnenuntergang am Meer
-
-
-
-
-
-
-
-
-
-
-
-
- 4 -
-
- - - - Visible - Privat - - -
-
- -
Fernsehen zu Zweit mit Nebenwirkungen
-
-
-
-
-
-
-
-
-
-
-
-
- 7 -
-
- - - - Visible - Privat - - -
-
- -
Spiele 1 und 2 und 3
-
-
-
-
-
-
-
-
-
-
-
-
- 14 -
-
- - - - Visible - Privat - - -
-
- -
-

Add new Page

- - -
-
- - Dies ist der Footer. - -
- - - - -

Page: Fernsehen zu Zweit mit Nebenwirkungen

-
- -
-
-
-
- -

Seitenname:

-
- - - - - ! - - -
- -
-
- -

Page Information

-
- -
Author
-
ID
-
SEO-URL
-
Status
-
- -
- - - - engineertrooper (Dominic Seela) - versustunez (Maurice Grönwoldt) - - -
-
- 7 -
-
- https://beispiel.seo-url.de/blabla/23455234 -
-
- - - - Visible - Privat - - -
-
- -
- - - -
-
- - Dies ist der Footer - -
- - - - -

Next header line

-
- - Hier steht Content - - - Dies ist der Footer - -
+
+
+ + +
\ No newline at end of file diff --git a/tpl/admin/jsTemplates/includes/btn.tpl b/tpl/admin/jsTemplates/includes/btn.tpl new file mode 100644 index 0000000..61f906e --- /dev/null +++ b/tpl/admin/jsTemplates/includes/btn.tpl @@ -0,0 +1,4 @@ + \ No newline at end of file diff --git a/tpl/admin/jsTemplates/includes/input.tpl b/tpl/admin/jsTemplates/includes/input.tpl new file mode 100644 index 0000000..8067657 --- /dev/null +++ b/tpl/admin/jsTemplates/includes/input.tpl @@ -0,0 +1,7 @@ + + \ No newline at end of file diff --git a/tpl/admin/jsTemplates/includes/select.tpl b/tpl/admin/jsTemplates/includes/select.tpl new file mode 100644 index 0000000..552c5f5 --- /dev/null +++ b/tpl/admin/jsTemplates/includes/select.tpl @@ -0,0 +1,8 @@ + + + + {foreach(object as item)} + ${item.name} + {/for} + + \ No newline at end of file diff --git a/tpl/admin/jsTemplates/includes/svg.tpl b/tpl/admin/jsTemplates/includes/svg.tpl new file mode 100644 index 0000000..269d709 --- /dev/null +++ b/tpl/admin/jsTemplates/includes/svg.tpl @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/tpl/admin/jsTemplates/rolesList.tpl b/tpl/admin/jsTemplates/rolesList.tpl new file mode 100644 index 0000000..6cf73d9 --- /dev/null +++ b/tpl/admin/jsTemplates/rolesList.tpl @@ -0,0 +1,23 @@ + + +

Roles

+
+ +
+

Overview

+ {foreach(roles as role,key)} +
+ + {include(includes/svg;icon=$role.icon)} + + ${role.name} +
+ {/for} +
+
+

Add new Role

+ {include(includes/input;label=New Role Name;name=newRoleame;error=New Role Name is required;type=text)} + {include(includes/btn;type=primary;content=Add)} +
+
+
\ No newline at end of file diff --git a/tpl/admin/login.php b/tpl/admin/login.php index 68035ac..00c2321 100644 --- a/tpl/admin/login.php +++ b/tpl/admin/login.php @@ -4,16 +4,21 @@

Login Failed

-
- - - Username is required -
-
- - - Password is required -
+ + + +