- Lean's API WIP on forms and ApiRenderer
This commit is contained in:
1 parent
3ce356e1f2
commit
4d7053a9cb
30 files changed
+1003
-56
No files matched your search
@@ -6,8 +6,10 @@
|
||||
|
||||
<div class="forms">
|
||||
{% foreach $forms as $name => $form %}
|
||||
<div class="single-form" style="padding-left:15px;border-left: 3px solid #9ccce6;">
|
||||
<h4 class='form-name'>{{ $name }}</h4>
|
||||
<div class="single-form" style="padding-left:15px;border-left: 3px solid #9ccce6;" data-form="{% json.html $form %}">
|
||||
<h4 class='form-name'>
|
||||
<span>{{ $name }}</span>
|
||||
</h4>
|
||||
<div class='description'>{{ $form['description'] }}</div>
|
||||
<hr style="margin-top:15px">
|
||||
|
||||
@@ -25,12 +27,24 @@
|
||||
|
||||
<div class="field-desc" style="margin-top:10px;;background:#fff;padding:5px;font-size:0.9em">
|
||||
<div><u>Variable POST / champ JSON</u> : {{ $field['name'] }}</div>
|
||||
<div><u>Type(s)</u> : {{ $field['type'] }}</div>
|
||||
<div><u>Type(s)</u> : {{ implode(' | ', array_map(fn($e) => $e->type, $field['type'])) }}</div>
|
||||
<div><u>Nullable</u> : {{ yesOrNo($field['allowNulls']) }}</div>
|
||||
<div><u>Pattern regex</u> : {{ $field['regexPattern'] }}</div>
|
||||
<div><u>Taille min.</u> : {{ $field['minLength'] }}</div>
|
||||
<div><u>Taille max.</u> : {{ $field['maxLength'] }}</div>
|
||||
<div><u>Exemple</u> : {{ $field['example'] }}</div>
|
||||
{% if $field['regexPattern'] !== null %}
|
||||
<div><u>Pattern regex</u> : {{ $field['regexPattern'] }}</div>
|
||||
{% endif %}
|
||||
{% if $field['minLength'] !== null %}
|
||||
<div><u>Taille min.</u> : {{ $field['minLength'] }}</div>
|
||||
{% endif %}
|
||||
{% if $field['maxLength'] !== null %}
|
||||
<div><u>Taille max.</u> : {{ $field['maxLength'] }}</div>
|
||||
{% endif %}
|
||||
{% if $field['example'] !== null || $field['values'] %}
|
||||
<div><u>Exemple</u> : {{ $field['example'] }}</div>
|
||||
{% endif %}
|
||||
|
||||
{% if $field['values'] %}
|
||||
<div><u>Valeurs possibles</u> : [ <u>{{= implode('</u>, <u>', $field['values']) }}</u> ]</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</li>
|
||||
{% endforeach %}
|
||||
|
||||
@@ -0,0 +1,241 @@
|
||||
{% use Ulmus\Api\Common\MethodEnum %}
|
||||
|
||||
{% language.set "lean.api.request.debugger" %}
|
||||
|
||||
<div id="request-debugger" class="hide">
|
||||
<div class="request-head">
|
||||
<div class="method" style="">
|
||||
{% ui.select "method", MethodEnum::generateArray(), post('method') %}
|
||||
</div>
|
||||
|
||||
<div class="url" style="flex-grow: 1;">
|
||||
{% ui:text "url" %}
|
||||
<button class="request-btn">Envoyer</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="request-content">
|
||||
<div id="request" style="min-height: 150px; width:100%; border:1px solid #ededed;">{}</div>
|
||||
</div>
|
||||
|
||||
<div class="request-response hide">
|
||||
<hr>
|
||||
<pre id="response" class="code" ace-theme="ace/theme/cloud9_day"></pre>
|
||||
|
||||
<div class="response-head">
|
||||
<span class="response-code"></span>
|
||||
<span class="response-message"></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.request-head {display:flex;padding-top: 4px;border:1px solid #e1e1e1;border-bottom:0}
|
||||
.request-head .method {width: 90px;text-align: center;line-height: 34px;font-weight: bold;}
|
||||
.request-head .method select {background:none;border:0;font-size: inherit;text-align: center;color: inherit;font-weight: bold;}
|
||||
.request-head .url input {width: calc(100% - 94px); border:0;color: #6f6f6f;background: #f9f9f9;font-size: 80%;font-weight: bold;}
|
||||
.request-head .url button {font-size: 80%;width: 80px;border-radius:0;border: 1px solid #ccc;height:26px;padding: 0;background:#f9f9f9;color: #6f6f6f;cursor: pointer;}
|
||||
.request-head .url button:active {filter:contrast(85%);}
|
||||
|
||||
.response-head {display: flex;border:1px solid #9f9f9f;margin-bottom: 5px;}
|
||||
.response-head .response-code {background:#dfdfdf;padding:0 5px;font-weight: bold;line-height: 29px;font-size: 80%;}
|
||||
.response-head .response-message {padding:0 10px;background:#fbfbfb;line-height: 30px;font-size: 80%;}
|
||||
|
||||
#response {max-height: 66vh;}
|
||||
</style>
|
||||
|
||||
<script src="{% asset 'static/ace/src-noconflict/ace.js' %}" type="text/javascript" charset="utf-8"></script>
|
||||
<script src="{% asset 'static/ace/src-noconflict/ext-static_highlight.js' %}" type="text/javascript" charset="utf-8"></script>
|
||||
|
||||
<script>
|
||||
let requestDebugger = document.getElementById('request-debugger'),
|
||||
requestHead = requestDebugger.querySelector(".request-head"),
|
||||
requestContent = requestDebugger.querySelector(".request-content"),
|
||||
responseHead = requestDebugger.querySelector(".response-head"),
|
||||
responseResponse = requestDebugger.querySelector(".request-response"),
|
||||
method = requestHead.querySelector(".method"),
|
||||
input = requestHead.querySelector("[name='url']"),
|
||||
button = requestHead.querySelector(".request-btn");
|
||||
|
||||
// Editor
|
||||
let editor = ace.edit("request");
|
||||
editor.setTheme("ace/theme/cloud9_day");
|
||||
editor.session.setMode("ace/mode/json");
|
||||
|
||||
// Code highlighter
|
||||
let highlight = ace.require("ace/ext/static_highlight"),
|
||||
dom = ace.require("ace/lib/dom"),
|
||||
responseEditorElement = responseResponse.querySelector('#response');
|
||||
|
||||
input.addEventListener('keydown', evt => evt.keyCode === 13 ? button.click() : null);
|
||||
|
||||
document.addEventListener("DOMContentLoaded", (evt) => {
|
||||
document.querySelectorAll(".form-name").forEach(elem => {
|
||||
elem.insertAdjacentHTML('beforeend', '<a href="javascript:void(0)" class="btn debug">DEBUG</a>');
|
||||
|
||||
let formData = JSON.parse(elem.closest('.single-form').getAttribute('data-form'));
|
||||
|
||||
elem.querySelector(".btn.debug").addEventListener("click", (btn) => {
|
||||
requestDebugger.classList.toggle('hide', false);
|
||||
|
||||
let json = {};
|
||||
|
||||
formData.fields.forEach((field) => {
|
||||
json[field.name] = field.allowNulls ? null : "";
|
||||
});
|
||||
|
||||
editor.setValue(JSON.stringify(json,null,2), -1);
|
||||
});
|
||||
});
|
||||
|
||||
document.querySelectorAll("li[class*='method-']").forEach( (url) => {
|
||||
url.addEventListener("click", (evt) => {
|
||||
evt.preventDefault();
|
||||
|
||||
let routeMethod = url.querySelector('.route-method');
|
||||
|
||||
method.style.color = getComputedStyle(routeMethod).getPropertyValue('background-color');
|
||||
method.querySelector('select').value = routeMethod.innerText;
|
||||
|
||||
input.value = url.querySelector('.route-link a').getAttribute('href');
|
||||
|
||||
document.getElementById('request-debugger').classList.toggle('hide', false);
|
||||
|
||||
replaceUrlVariableUsingData();
|
||||
|
||||
input.focus();
|
||||
});
|
||||
});
|
||||
|
||||
let aceMode;
|
||||
|
||||
button.addEventListener("click", (evt) => {
|
||||
let requestMethod = method.querySelector('select').value;
|
||||
|
||||
if (requestMethod === 'DELETE' && ! confirm("Attention ! Vous allez lancer une procédure de suppression de données.\n\nContinuer ?")) {
|
||||
return;
|
||||
}
|
||||
|
||||
launchRequest(requestMethod, input.value, editor.getValue())
|
||||
.then((response) => {
|
||||
responseHead.querySelector('.response-code').innerText = response.status;
|
||||
responseHead.querySelector('.response-message').innerText = response.statusText;
|
||||
|
||||
aceMode = parseContentType(response);
|
||||
|
||||
return response.text();
|
||||
})
|
||||
.then(body => {
|
||||
if (aceMode === "json") {
|
||||
body = JSON.stringify(JSON.parse(body), null, 2);
|
||||
responseEditorElement.innerHTML = body;
|
||||
}
|
||||
else {
|
||||
responseEditorElement.innerText = body;
|
||||
}
|
||||
|
||||
formatResponse("ace/mode/" + aceMode);
|
||||
});
|
||||
|
||||
responseResponse.classList.toggle('hide', false);
|
||||
});
|
||||
|
||||
let selectRoute = getQueryVariable("debug.route"),
|
||||
presetData = getQueryVariable("debug.data");
|
||||
|
||||
if ( selectRoute ) {
|
||||
if (presetData) {
|
||||
editor.setValue(presetData);
|
||||
}
|
||||
|
||||
document.querySelector(`[data-name="${selectRoute}"]`).click();
|
||||
}
|
||||
});
|
||||
|
||||
async function launchRequest(method = "POST", url = "", body = "{}") {
|
||||
method = method.toUpperCase();
|
||||
|
||||
let responseData = {
|
||||
method: method,
|
||||
mode: "cors",
|
||||
cache: "no-cache",
|
||||
credentials: "same-origin",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
'Authorization': 'Bearer {{ $this->session->jwt }}'
|
||||
},
|
||||
redirect: "follow",
|
||||
referrerPolicy: "no-referrer"
|
||||
};
|
||||
|
||||
if ( [ "HEAD", "GET" ].indexOf(method) === -1 ) {
|
||||
responseData.body = body;
|
||||
}
|
||||
|
||||
return await fetch(url, responseData);
|
||||
}
|
||||
|
||||
function formatResponse(mode) {
|
||||
highlight(responseEditorElement, {
|
||||
mode: mode,
|
||||
theme: responseEditorElement.getAttribute("ace-theme"),
|
||||
firstLineNumber: 1,
|
||||
showGutter: responseEditorElement.getAttribute("ace-gutter"),
|
||||
trim: true
|
||||
});
|
||||
}
|
||||
|
||||
function parseContentType(response) {
|
||||
let type = response.headers.get('content-type').toLowerCase();
|
||||
|
||||
if ( type.indexOf('text/html') !== -1 ) {
|
||||
return "html";
|
||||
}
|
||||
else if ( type.indexOf('text/css') !== -1 ) {
|
||||
return "css";
|
||||
}
|
||||
else if ( type.indexOf('text/csv') !== -1 ) {
|
||||
return "csv";
|
||||
}
|
||||
else if ( type.indexOf('text/xml') !== -1 ) {
|
||||
return "xml";
|
||||
}
|
||||
else if ( type.indexOf('text/plain') !== -1 ) {
|
||||
return "text";
|
||||
}
|
||||
else if ( type.indexOf('application/javascript') !== -1 ) {
|
||||
return "javascript";
|
||||
}
|
||||
else if ( type.indexOf('application/json') !== -1 || type.indexOf('application/ld+json') !== -1 ) {
|
||||
return "json";
|
||||
}
|
||||
}
|
||||
|
||||
function getQueryVariable(variable) {
|
||||
retval = false;
|
||||
|
||||
window.location.search.substring(1).split("&").forEach(vars => {
|
||||
var pair = vars.split("=");
|
||||
|
||||
if (pair.length === 2 && pair[0] === variable) {
|
||||
return retval = decodeURI(pair[1]);
|
||||
}
|
||||
});
|
||||
|
||||
return retval;
|
||||
}
|
||||
|
||||
function replaceUrlVariableUsingData() {
|
||||
try {
|
||||
let vars = input.value.match(/[^{}]+(?=})/g),
|
||||
body = JSON.parse(editor.getValue());
|
||||
|
||||
vars.forEach(v => {
|
||||
if (body[v]) {
|
||||
input.value = input.value.replace("{" + v + "}", body[v]);
|
||||
}
|
||||
})
|
||||
}
|
||||
catch(e) {}
|
||||
}
|
||||
</script>
|
||||
@@ -3,19 +3,33 @@
|
||||
<ul class="routes-wrapper">
|
||||
{% foreach $routes as $route %}
|
||||
{% foreach $route['methods'] as $method %}
|
||||
<li class="method-{{ strtolower($method) }}">
|
||||
<li class="method-{{ strtolower($method) }}" data-name="{{ $route['name'] }}">
|
||||
<span class="route-method">
|
||||
<span class="method-name">{{ strtoupper($method) }}</span>
|
||||
</span>
|
||||
|
||||
<span class="route-link">
|
||||
<a href="{{ $route['route'] }}" title="{{ $route['path'] }}">{{ $route['cleaned'] }}</a>
|
||||
<span>-</span>
|
||||
<span>{{= $route['description'] }}</span>
|
||||
</span>
|
||||
<small class="route-name">{{ $route['name'] }}</small>
|
||||
|
||||
<small class="route-name">
|
||||
<span>{{ $route['name'] }}</span>
|
||||
<br>
|
||||
<span class="permissions">
|
||||
{% foreach ['admin', 'school' ] as $privilege %}
|
||||
<u class="privilege">{{ $privilege }}</u>
|
||||
{% endforeach %}
|
||||
</span>
|
||||
</small>
|
||||
</li>
|
||||
{% or %}
|
||||
<i style="color:#585858; padding:0 12px">{% _ "none" %}</i>
|
||||
{% endforeach %}
|
||||
{% endforeach %}
|
||||
</ul>
|
||||
</ul>
|
||||
|
||||
<style>
|
||||
.permissions {color:#888}
|
||||
</style>
|
||||
@@ -16,6 +16,7 @@
|
||||
<meta name="theme-color" content="#ffffff">
|
||||
|
||||
<style>
|
||||
*, *:before, *:after {box-sizing: border-box;}
|
||||
a{color:#e44a00}h3{background:#e1e1e1;padding:6px 12px}ul{background:#e9e9fd;padding-top:20px;padding-bottom:20px;border:1px solid #e3e3ec}ul li + li{margin-top:8px}li > em{font-size:0.75rem;color:gray}body{font-family:Helvetica, 'Helvetica Neuve', Arial, Tahoma, sans-serif;font-size:17px;color:#333}h1,h2,h3,h4,h5,h6{color:#222;margin:0 0 20px}dl,ol,p,pre,table,ul{margin:0 0 20px}h1,h2,h3{line-height:1.1}h1{font-size:20px;text-align:right;color:#387eea;font-weight:bold}h2{color:#393939}h3,h4,h5,h6{color:#494949}h3{display:flex}h3 > code{margin-right:5px;color:#b52dac}h3 > strong{margin-left:auto}a{color:#39c;font-weight:400;text-decoration:none}a small{font-size:11px;color:#777;margin-top:-0.6em;display:block}.wrapper{width:860px;margin:0 auto}blockquote{border-left:1px solid #e5e5e5;margin:0;padding:0 0 0 20px;font-style:italic}code,pre{font-size:12px}pre{padding:8px 15px;background:#f8f8f8;border-radius:5px;border:1px solid #e5e5e5;overflow-x:auto}table{width:100%;border-collapse:collapse}td,th{text-align:left;padding:5px 10px;border-bottom:1px solid #e5e5e5}dt{color:#444;font-weight:700}th{color:#444}img{max-width:100%}header{width:270px;float:left;position:fixed}header ul{list-style:none;height:40px;padding:0;background:#eee;background:-moz-linear-gradient(top, #f8f8f8 0%, #dddddd 100%);background:-webkit-gradient(linear, left top, left bottom, color-stop(0%,#f8f8f8), color-stop(100%,#dddddd));background:-webkit-linear-gradient(top, #f8f8f8 0%,#dddddd 100%);background:-o-linear-gradient(top, #f8f8f8 0%,#dddddd 100%);background:-ms-linear-gradient(top, #f8f8f8 0%,#dddddd 100%);background:linear-gradient(top, #f8f8f8 0%,#dddddd 100%);border-radius:5px;border:1px solid #d2d2d2;box-shadow:inset #fff 0 1px 0, inset rgba(0,0,0,0.03) 0 -1px 0;width:270px}header li{width:89px;float:left;border-right:1px solid #d2d2d2;height:40px}header ul a{line-height:1;font-size:11px;color:#999;display:block;text-align:center;padding-top:6px;height:40px}strong{color:#222;font-weight:700}header ul li + li{width:88px;border-left:1px solid #fff}header ul li + li + li{border-right:none;width:89px}header ul a strong{font-size:14px;display:block;color:#222}section{width:500px;float:right;padding-bottom:50px}small{font-size:11px}hr{border:0;background:#e5e5e5;height:1px;margin:0 0 20px}footer{width:270px;float:left;position:fixed;bottom:50px}@media print, screen and (max-width: 960px){div.wrapper{width:auto;margin:0}footer,header,section{float:none;position:static;width:auto}header{padding-right:320px}section{border:1px solid #e5e5e5;border-width:1px 0;padding:20px 0;margin:0 0 20px}header a small{display:inline}header ul{position:absolute;right:50px;top:52px}}@media print, screen and (max-width: 720px){body{word-wrap:break-word}header{padding:0}header p.view,header ul{position:static}code,pre{word-wrap:normal}}@media print, screen and (max-width: 480px){body{padding:15px}header ul{display:none}}@media print{body{padding:0.4in;font-size:12pt;color:#444}}#wrapper{margin-left:auto;margin-right:auto;background-color:white}.ca-menu{list-style:none;padding:0;margin:20px auto}#navi{padding-top:15px;padding-right:15px;float:right;width:420px}#title{padding-left:15px;width:460px;float:left}div.clear{clear:both}h2{font-size:2em}h3{font-size:1.5em}h4{font-size:1.2em}h5{font-size:1em;font-weight:bold}h6{font-size:1em;font-weight:bold}h1,h2,h3,h4,h5,h6{font-weight:normal;line-height:2.5rem;margin:1rem 0}.post p{max-width:580px}ol.list,ul.list{padding-left:3.333em;max-width:580px}.post h2{border-bottom:1px solid #EDEDED}h1:nth-child(1),h2:nth-child(1),h3:nth-child(1),h4:nth-child(1),h5:nth-child(1),h6:nth-child(1){margin-top:0}body{padding:1em}#wrapper{padding:1em}@media (min-width: 43.75em){body{padding:2em}#wrapper{padding:2em}}@media (min-width: 62em){body{padding:3em}#wrapper{max-width:740px;padding:3em}}
|
||||
ol{background: #eff4f2;padding-top:20px;padding-bottom:20px;border:1px solid #e3e3ec}
|
||||
h4{background:#e0f7ed;padding:6px 12px; font-weight: bold!important;font-size:100%;margin-top:0}
|
||||
@@ -23,7 +24,9 @@
|
||||
h3 {display: flex;align-items: center;height: 60px;padding: 0 15px 0 15px;font-variant: small-caps;}
|
||||
li.odd-even{border-top:1px solid #ccc;margin:10px 0;padding:15px 15px 10px 5px}
|
||||
li.odd-even:first-child{border:0}
|
||||
input, button {padding:5px; font-size:1em;margin-top:10px}
|
||||
input, button {padding:5px; font-size:1em;margin:0}
|
||||
|
||||
.hide {display:none!important}
|
||||
|
||||
ul {background:#f4f4f4; list-style: none; padding-left:20px}
|
||||
ul ul {margin: 0;border: 0;padding: 5px 30px;}
|
||||
@@ -32,16 +35,19 @@
|
||||
.field-desc > div {padding:5px;}
|
||||
.forms ol {background: #ccdef2;}
|
||||
.forms li {border-color: #859aae;}
|
||||
.forms .form-name {background: #9cc5e6;color: #284168;font-size:110%}
|
||||
.forms .form-name {background: #9cc5e6;display:flex;justify-content: space-between}
|
||||
.forms .form-name span {color: #284168;font-size:110%}
|
||||
.forms .form-name .btn {background:rgba(50,50,50,0.5);padding:0 15px;border: 1px solid rgba(50,50,50,0.8);font-family: 'Déja Vu', 'Courier New', Courier, monospace, serif;font-size: 90%;color: #fff;text-decoration: underline;}
|
||||
|
||||
.routes-wrapper {padding:8px}
|
||||
.routes-wrapper li {display:flex;border: 1px solid #ccc;align-items: stretch;}
|
||||
.routes-wrapper li {display:flex;border: 1px solid #ccc;align-items: stretch;cursor:pointer}
|
||||
.routes-wrapper li:hover {filter: contrast(85%)}
|
||||
.routes-wrapper li + li {margin-top: 5px;}
|
||||
.routes-wrapper .route-method {display:flex;align-items: center; justify-content: center; line-height: 1.8rem;padding:6px 5px 0 5px;min-width:80px;text-align:center;font-weight:bold;color:#fff;}
|
||||
.routes-wrapper .route-link {line-height: 1.8rem;padding:0 10px;display: flex;align-items: center;}
|
||||
.routes-wrapper .route-link span {margin-left: 7px;}
|
||||
.routes-wrapper .route-link a {font-family:monospace;font-size:.85em}
|
||||
.routes-wrapper .route-name {margin-left:auto;font-weight:bold;min-width: 20%;text-align: right;background:rgba(0, 0, 0, 0.02);line-height: 1.8rem;padding:6px 7px 0 5px;}
|
||||
.routes-wrapper .route-link a {font-family:monospace;font-size:.85em;white-space: nowrap;}
|
||||
.routes-wrapper .route-name {margin-left:auto;font-weight:bold;min-width: 20%;text-align: right;background:rgba(0, 0, 0, 0.02);line-height: 1.4rem;padding:6px 7px 0 5px;align-content: center;}
|
||||
.routes-wrapper li.method-get {background:#e7eff7;border-color: #bfcfdd;}
|
||||
.routes-wrapper li.method-get .route-method {background:#0f6ab4;}
|
||||
.routes-wrapper li.method-get .route-link a, .routes-wrapper li.method-get .route-name {color: #0f6ab4;}
|
||||
|
||||
Reference in new issue
Block a user