Compare commits
17 Commits
Author | SHA1 | Date |
---|---|---|
Harald Wolff | de0219f509 | |
Harald Wolff | 63f02b047e | |
Harald Wolff | 6ff597c106 | |
Harald Wolff | 4f67439042 | |
Harald Wolff | b870fe214c | |
Harald Wolff | db1f67805b | |
Harald Wolff | caff9e19d5 | |
Harald Wolff | f001840670 | |
Harald Wolff | d1663cbbbe | |
Harald Wolff | 0de72abc3d | |
Harald Wolff | 9bb9a877af | |
Harald Wolff | 1f3608bc21 | |
Harald Wolff | 77724600d1 | |
Harald Wolff | e85c00a5f3 | |
Harald Wolff | bcce34d170 | |
Harald Wolff | 51df83f9e7 | |
Harald Wolff | 75924bc19a |
237
css/ln.vue.css
237
css/ln.vue.css
|
@ -36,6 +36,58 @@
|
|||
font-style: normal;
|
||||
}
|
||||
|
||||
* {
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
div {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
h1, h2, h3, h4, h5 {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.h1, .h2, .h3, .h4, .h5 {
|
||||
display: inline-block;
|
||||
|
||||
}
|
||||
|
||||
h1,.h1 {
|
||||
font-size: 200%;
|
||||
padding: 8px;
|
||||
}
|
||||
h2,.h2 {
|
||||
font-size: 150%;
|
||||
padding: 8px;
|
||||
}
|
||||
h3,.h3 {
|
||||
font-size: 110%;
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
.flex {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.flex.column {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.popup {
|
||||
position: absolute;
|
||||
top: 100%;
|
||||
right: 0%;
|
||||
|
||||
width: 100%;
|
||||
|
||||
border: 1px solid black;
|
||||
background-color: white;
|
||||
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
sym {
|
||||
display: inline-block;
|
||||
width: 24px;
|
||||
|
@ -49,6 +101,18 @@ sym.trash::before {
|
|||
color: red;
|
||||
}
|
||||
|
||||
.no-border {
|
||||
border: none;
|
||||
display: inline-block;
|
||||
padding: 0px;
|
||||
margin: 0px;
|
||||
}
|
||||
|
||||
.fa-solid {
|
||||
font-family: 'fa-solid';
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
div.ln-tooltip {
|
||||
position: absolute;
|
||||
|
||||
|
@ -58,7 +122,14 @@ div.ln-tooltip {
|
|||
border-radius: 0px 6px 6px 6px;
|
||||
border: 1px solid black;
|
||||
|
||||
transition: opacity 500ms;
|
||||
opacity: 0.0;
|
||||
transition: opacity 300ms linear;
|
||||
|
||||
pointer-events: none;
|
||||
}
|
||||
div.ln-tooltip[VISIBLE] {
|
||||
opacity: 1.0;
|
||||
transition: opacity 300ms linear;
|
||||
}
|
||||
|
||||
body {
|
||||
|
@ -66,6 +137,37 @@ body {
|
|||
font-size: 12px;
|
||||
}
|
||||
|
||||
.block {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.ln-view {
|
||||
position: absolute;
|
||||
top: 0px;
|
||||
bottom: 0px;
|
||||
left: 0px;
|
||||
right: 0px;
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
section#header {
|
||||
flex-grow: 0;
|
||||
margin-bottom: 1em;
|
||||
background-color: whitesmoke;
|
||||
}
|
||||
|
||||
section#body {
|
||||
flex-grow: 1;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
section#footer {
|
||||
flex-grow: 0;
|
||||
background-color: whitesmoke;
|
||||
}
|
||||
|
||||
.json {
|
||||
white-space: pre;
|
||||
font-family: 'Courier New', Courier, monospace;
|
||||
|
@ -136,6 +238,20 @@ div.ln-select::after {
|
|||
pointer-events: none;
|
||||
}
|
||||
|
||||
.ln-identity {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
flex-grow: 0;
|
||||
flex-shrink: 0;
|
||||
width: 200px;
|
||||
|
||||
text-align: center;
|
||||
|
||||
padding: 8px;
|
||||
|
||||
font-size: 150%;
|
||||
}
|
||||
|
||||
.ln-navbar {
|
||||
display: block;
|
||||
width: 100%;
|
||||
|
@ -143,10 +259,13 @@ div.ln-select::after {
|
|||
border-top: 1px solid black;
|
||||
border-bottom: 1px solid black;
|
||||
|
||||
margin-bottom: 2em;
|
||||
padding-top: 4px;
|
||||
padding-bottom: 4px;
|
||||
|
||||
}
|
||||
|
||||
.ln-navitem {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
|
||||
font-family: Arial, Helvetica, sans-serif;
|
||||
|
@ -160,8 +279,122 @@ div.ln-select::after {
|
|||
.ln-navitem > a {
|
||||
font-style: normal;
|
||||
text-decoration: none;
|
||||
color: black;
|
||||
}
|
||||
|
||||
div.ln-navitem > div.ln-nav-children {
|
||||
position: absolute;
|
||||
|
||||
white-space: pre;
|
||||
left: 0px;
|
||||
top: 100%;
|
||||
opacity: 0.0;
|
||||
|
||||
transition: opacity 300ms;
|
||||
|
||||
border: 1px solid black;
|
||||
border-top: none;
|
||||
background-color: white;
|
||||
|
||||
padding: 8px;
|
||||
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
div.ln-navitem:hover > div.ln-nav-children {
|
||||
opacity: 1.0;
|
||||
|
||||
transition: opacity 300ms;
|
||||
pointer-events: visible;
|
||||
}
|
||||
|
||||
div.ln-nav-children > .ln-navitem {
|
||||
display: block;
|
||||
margin-top: 4px;
|
||||
padding-top: 4px;
|
||||
border-top: 1px solid #C0C0C0;
|
||||
}
|
||||
div.ln-nav-children > .ln-navitem:first-of-type {
|
||||
margin-top: 0px;
|
||||
border-top: none;
|
||||
}
|
||||
|
||||
div.ln-statusbar {
|
||||
position: relative;
|
||||
bottom: 0px;
|
||||
left: 0px;
|
||||
right: 0px;
|
||||
|
||||
border-top: 1px solid black;
|
||||
|
||||
padding: 8px;
|
||||
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
}
|
||||
div.ln-statusbar > div {
|
||||
flex-grow: 1;
|
||||
text-align: center;
|
||||
|
||||
padding: 4px;
|
||||
padding-left: 8px;
|
||||
padding-right: 8px;
|
||||
|
||||
border-left: 1px solid #D0D0D0;
|
||||
}
|
||||
div.ln-statusbar > div:first-of-type {
|
||||
text-align: left;
|
||||
border-left: none;
|
||||
}
|
||||
|
||||
div.ln-statusbar > div.ln-background-tasks {
|
||||
flex-grow: 0;
|
||||
}
|
||||
|
||||
.ln-background-tasks {
|
||||
position: relative;
|
||||
}
|
||||
div.ln-background-tasks > div {
|
||||
display: block;
|
||||
position: absolute;
|
||||
|
||||
white-space: pre;
|
||||
|
||||
left: 4px;
|
||||
bottom: 80%;
|
||||
|
||||
overflow-y: visible;
|
||||
overflow-x: hidden;
|
||||
|
||||
background-color: blanchedalmond;
|
||||
border: 1px solid black;
|
||||
|
||||
padding: 4px;
|
||||
padding-right: 32px;
|
||||
padding-left: 16px;
|
||||
|
||||
margin-right: 24px;
|
||||
|
||||
opacity: 0;
|
||||
height: 10px;
|
||||
|
||||
transition: opacity 500ms 50ms, height 0ms 550ms;
|
||||
}
|
||||
div.ln-background-tasks:hover > div {
|
||||
opacity: 1.0;
|
||||
height: 8em;
|
||||
|
||||
transition: opacity 500ms 50ms, height 500ms 50ms;
|
||||
}
|
||||
div.ln-background-tasks > div > div[state="waiting"] {
|
||||
color: silver;
|
||||
}
|
||||
div.ln-background-tasks > div > div[state="failed"] {
|
||||
color: red;
|
||||
}
|
||||
div.ln-background-tasks > div > div[state="ready"] {
|
||||
color: green;
|
||||
}
|
||||
|
||||
div.ln-upload {
|
||||
display: inline-block;
|
||||
|
|
168
demo.html
168
demo.html
|
@ -5,51 +5,95 @@
|
|||
<meta charset="UTF-8">
|
||||
<link rel="stylesheet" href="css/ln.vue.css"/>
|
||||
|
||||
<script type="text/javascript" src="js/vue.js"></script>
|
||||
<script type="text/javascript" src="js/vue-router.js"></script>
|
||||
|
||||
<script type="text/javascript" src="js/ln.vue.application.js"></script>
|
||||
<script type="text/javascript" src="js/ln.vue.components.js"></script>
|
||||
<script type="text/javascript" src="js/ln.vue.table.js"></script>
|
||||
<script type="text/javascript" src="js"></script>
|
||||
|
||||
<style>
|
||||
table#controls > tbody > tr > td:nth-child(3) {
|
||||
padding-left: 24px;
|
||||
}
|
||||
|
||||
div#frame {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
</style>
|
||||
|
||||
</head>
|
||||
<body>
|
||||
<div id="frame">
|
||||
<h1>ln.vue Demo Application</h1>
|
||||
<div id="frame" class="ln-view">
|
||||
<section id="header">
|
||||
<div class="flex">
|
||||
<div class="h1">ln.vue Demo Application</div>
|
||||
<ln-identity></ln-identity>
|
||||
</div>
|
||||
<ln-navbar></ln-navbar>
|
||||
</section>
|
||||
<section id="body">
|
||||
<div v-if="false">
|
||||
<h2>Please wait for application to be loaded...</h2>
|
||||
</div>
|
||||
|
||||
<ln-navbar
|
||||
:navitems="[
|
||||
{ href: '/', label: 'HOME' },
|
||||
{ href: '/controls', label: 'Simple Form Controls' },
|
||||
{ href: '/table', label: 'Table View Control' },
|
||||
{ href: '/state', label: 'Current State' },
|
||||
]"></ln-navbar>
|
||||
<router-view></router-view>
|
||||
<div id="viewPane" class="ln-viewpane">
|
||||
<router-view></router-view>
|
||||
</div>
|
||||
</section>
|
||||
<section id="footer">
|
||||
<ln-statusbar></ln-statusbar>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<script type="text/javascript">
|
||||
LNVue.addRouteTemplate(
|
||||
"/",
|
||||
`<div>This is the ln.vue demo application. Feel free to test all the features, you can find.</div>`
|
||||
);
|
||||
LNVue.addRouteTemplate(
|
||||
"/state",
|
||||
`<div>Current User Input object:<br><br>
|
||||
<span class="json">{{ JSON.stringify(controls,null,4) }}</span>
|
||||
</div>`
|
||||
);
|
||||
let core = {
|
||||
navigation: {
|
||||
core: {
|
||||
label: "HOME",
|
||||
path: '/',
|
||||
},
|
||||
},
|
||||
routes: {
|
||||
'/': {
|
||||
template: `<div>This is the ln.vue demo application.<br>
|
||||
Feel free to test all the features, you can find.<br>
|
||||
Here should be a message:<br>
|
||||
<i>{{ message.value }}</i><br>
|
||||
<br>
|
||||
<div v-if="LN.Vue.$_.identity.hasRole(LN.Identity.VIEW,'demo')">You are allowed to see this!</div>
|
||||
<div v-if="!LN.Vue.$_.identity.hasRole(LN.Identity.VIEW,'demo')">Login using demo/demopass to see the hidden message!</div>
|
||||
</div>`,
|
||||
},
|
||||
}
|
||||
};
|
||||
let controls = {
|
||||
navigation: {
|
||||
controls: {
|
||||
label: "Controls",
|
||||
navigation: {
|
||||
simple: {
|
||||
label: "Simple Form Controls",
|
||||
path: '/controls',
|
||||
},
|
||||
table: {
|
||||
label: "Table Contol",
|
||||
path: '/table',
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
routes: {
|
||||
'/controls': {
|
||||
url: '/controls.html',
|
||||
},
|
||||
'/table': {
|
||||
url: '/table.html',
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
LNVue.addRoute("/controls","/controls.html");
|
||||
LNVue.addRoute("/table","/table.html");
|
||||
let message = { value: "" };
|
||||
|
||||
app = new LNVue("#frame",{
|
||||
app = new LN.Vue("#frame",{
|
||||
data: {
|
||||
message: message,
|
||||
columns: {
|
||||
FirstName: {
|
||||
label: 'Vorname',
|
||||
|
@ -123,6 +167,72 @@
|
|||
}
|
||||
}
|
||||
});
|
||||
|
||||
app.addModule(core);
|
||||
app.addModule(controls);
|
||||
|
||||
app.addModule({
|
||||
navigation: {
|
||||
state: {
|
||||
label: "State",
|
||||
path: '/state',
|
||||
},
|
||||
},
|
||||
routes: {
|
||||
'/state': `<div>Current User Input object:<br><br>
|
||||
<span class="json">{{ JSON.stringify(controls,null,4) }}</span>
|
||||
</div>`,
|
||||
}
|
||||
});
|
||||
|
||||
app.addModule({
|
||||
navigation: {
|
||||
protocols: {
|
||||
label: 'Protocols',
|
||||
navigation: {
|
||||
webauthn: {
|
||||
label: 'webauthn',
|
||||
path: '/protocols/webauthn',
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
routes: {
|
||||
'/protocols/webauthn': {
|
||||
url: '/webauthn.html',
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
app.addModule({
|
||||
navigation: {
|
||||
login: {
|
||||
label: "Login Pane",
|
||||
path: "/login",
|
||||
}
|
||||
},
|
||||
routes: {
|
||||
'/login': `<div>Login Pane<br><ln-login-pane style="border: 1px solid black;"></ln-login-pane></div>`,
|
||||
}
|
||||
});
|
||||
|
||||
app.Start();
|
||||
|
||||
LN.$idle(()=>{
|
||||
message.value = "idle time reached. Message written.";
|
||||
setTimeout(()=>{
|
||||
message.value = "fired from the idle job: new message after 1000ms.";
|
||||
},1000);
|
||||
});
|
||||
|
||||
for (let n=5;n<15;n++){
|
||||
new LN.Promise((resolve,reject)=>{
|
||||
setTimeout(()=>{
|
||||
resolve();
|
||||
},n * 1000);
|
||||
},n + " sec promise test");
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
</body>
|
||||
|
|
|
@ -0,0 +1,84 @@
|
|||
(function(){
|
||||
/*\
|
||||
|*|
|
||||
|*| Base64 / binary data / UTF-8 strings utilities (#1)
|
||||
|*|
|
||||
|*| https://developer.mozilla.org/en-US/docs/Web/API/WindowBase64/Base64_encoding_and_decoding
|
||||
|*|
|
||||
|*| Author: madmurphy
|
||||
|*|
|
||||
|*| Harald Wolff-Thobaben: Small adaptions to create a static class
|
||||
|*|
|
||||
\*/
|
||||
class Base64
|
||||
{
|
||||
constructor(){
|
||||
}
|
||||
|
||||
static b64ToUint6(nChr){
|
||||
return nChr > 64 && nChr < 91 ?
|
||||
nChr - 65
|
||||
: nChr > 96 && nChr < 123 ?
|
||||
nChr - 71
|
||||
: nChr > 47 && nChr < 58 ?
|
||||
nChr + 4
|
||||
: nChr === 43 ?
|
||||
62
|
||||
: nChr === 47 ?
|
||||
63
|
||||
:
|
||||
0;
|
||||
}
|
||||
static uint6ToB64(nUint6){
|
||||
return nUint6 < 26 ?
|
||||
nUint6 + 65
|
||||
: nUint6 < 52 ?
|
||||
nUint6 + 71
|
||||
: nUint6 < 62 ?
|
||||
nUint6 - 4
|
||||
: nUint6 === 62 ?
|
||||
43
|
||||
: nUint6 === 63 ?
|
||||
47
|
||||
:
|
||||
65;
|
||||
}
|
||||
|
||||
static encode(aBytes){
|
||||
var eqLen = (3 - (aBytes.length % 3)) % 3, sB64Enc = "";
|
||||
|
||||
for (var nMod3, nLen = aBytes.length, nUint24 = 0, nIdx = 0; nIdx < nLen; nIdx++) {
|
||||
nMod3 = nIdx % 3;
|
||||
nUint24 |= aBytes[nIdx] << (16 >>> nMod3 & 24);
|
||||
if (nMod3 === 2 || aBytes.length - nIdx === 1) {
|
||||
sB64Enc += String.fromCharCode(Base64.uint6ToB64(nUint24 >>> 18 & 63), Base64.uint6ToB64(nUint24 >>> 12 & 63), Base64.uint6ToB64(nUint24 >>> 6 & 63), Base64.uint6ToB64(nUint24 & 63));
|
||||
nUint24 = 0;
|
||||
}
|
||||
}
|
||||
|
||||
return eqLen === 0 ?
|
||||
sB64Enc
|
||||
:
|
||||
sB64Enc.substring(0, sB64Enc.length - eqLen) + (eqLen === 1 ? "=" : "==");
|
||||
}
|
||||
|
||||
static decode(sBase64, nBlockSize) {
|
||||
var
|
||||
sB64Enc = sBase64.replace(/[^A-Za-z0-9\+\/]/g, ""), nInLen = sB64Enc.length,
|
||||
nOutLen = nBlockSize ? Math.ceil((nInLen * 3 + 1 >>> 2) / nBlockSize) * nBlockSize : nInLen * 3 + 1 >>> 2, aBytes = new Uint8Array(nOutLen);
|
||||
|
||||
for (var nMod3, nMod4, nUint24 = 0, nOutIdx = 0, nInIdx = 0; nInIdx < nInLen; nInIdx++) {
|
||||
nMod4 = nInIdx & 3;
|
||||
nUint24 |= Base64.b64ToUint6(sB64Enc.charCodeAt(nInIdx)) << 18 - 6 * nMod4;
|
||||
if (nMod4 === 3 || nInLen - nInIdx === 1) {
|
||||
for (nMod3 = 0; nMod3 < 3 && nOutIdx < nOutLen; nMod3++, nOutIdx++) {
|
||||
aBytes[nOutIdx] = nUint24 >>> (16 >>> nMod3 & 24) & 255;
|
||||
}
|
||||
nUint24 = 0;
|
||||
}
|
||||
}
|
||||
return aBytes;
|
||||
}
|
||||
}
|
||||
LN.$add("LN.Base64",Base64);
|
||||
})();
|
|
@ -0,0 +1,51 @@
|
|||
(function(){
|
||||
|
||||
class LNIdentity
|
||||
{
|
||||
constructor(src){
|
||||
if (!src)
|
||||
src = {};
|
||||
this.IdentityName = src.IdentityName || "";
|
||||
this.UniqueID = src.UniqueID || null;
|
||||
this.Roles = src.AssignedRoles || [];
|
||||
}
|
||||
|
||||
findRolesByName(identityName){
|
||||
let roleMask = 0;
|
||||
this.Roles.forEach(role => {
|
||||
if (role.IdentityName == identityName)
|
||||
roleMask = role.Roles;
|
||||
});
|
||||
return roleMask;
|
||||
}
|
||||
findRolesByID(identityUniqueID){
|
||||
let roleMask = 0;
|
||||
this.Roles.forEach(role => {
|
||||
if (role.UniqueID == identityUniqueID)
|
||||
roleMask = role.Roles;
|
||||
});
|
||||
return roleMask;;
|
||||
}
|
||||
|
||||
hasRole(role,identityName){
|
||||
let roles = this.findRolesByName(identityName);
|
||||
return (roles & role) == role;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
LNIdentity.VIEW = (1<<0);
|
||||
LNIdentity.USE = LNIdentity.VIEW | (1<<1);
|
||||
LNIdentity.CONTROL = LNIdentity.VIEW | (1<<2);
|
||||
LNIdentity.MANAGE = LNIdentity.CONTROL | (1<<3);
|
||||
LNIdentity.ADMIN = 0x0000FFFF;
|
||||
|
||||
LNIdentity.MANAGEROLES = (1<<16);
|
||||
|
||||
LNIdentity.IMPERSONATE = (1<<24);
|
||||
LNIdentity.OWN = 0x0FFFFFFF;
|
||||
LNIdentity.BE = 0x0000FFFF;
|
||||
LNIdentity.SUPER = 0x7FFFFFFF;
|
||||
|
||||
LN.$add("LN.Identity",LNIdentity);
|
||||
})();
|
|
@ -0,0 +1,59 @@
|
|||
(function(){
|
||||
class LNPromise
|
||||
{
|
||||
constructor(executor, label){
|
||||
this.promise = new Promise(
|
||||
(resolve,reject) => {
|
||||
this.resolve = (v) => {
|
||||
this._s.state = "ready";
|
||||
resolve(v);
|
||||
this.release();
|
||||
},
|
||||
this.reject = (e) => {
|
||||
this._s.state = "failed";
|
||||
reject(e);
|
||||
this.release();
|
||||
}
|
||||
executor && executor(
|
||||
this.resolve,
|
||||
this.reject
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
if (!label)
|
||||
label = "N.D.";
|
||||
|
||||
this._s = {
|
||||
label,
|
||||
state: "waiting"
|
||||
};
|
||||
|
||||
this.idx = LN.$unique();
|
||||
LNPromise.$current[this.idx] = this._s;
|
||||
}
|
||||
|
||||
label(){
|
||||
return this._s.label;
|
||||
}
|
||||
|
||||
state(){
|
||||
return this._s.state;
|
||||
}
|
||||
|
||||
release(){
|
||||
setTimeout(()=>{
|
||||
Vue.delete(LNPromise.$current, this.idx);
|
||||
},1000);
|
||||
}
|
||||
|
||||
then(){
|
||||
return this.promise.then.apply(this.promise, arguments);
|
||||
}
|
||||
|
||||
static getCurrentPromises(){ return LNPromise.$current; }
|
||||
}
|
||||
LNPromise.$current = {};
|
||||
|
||||
LN.$add("LN.Promise",LNPromise);
|
||||
})();
|
|
@ -0,0 +1,724 @@
|
|||
(function(){
|
||||
|
||||
let nextTooltipKey = 0;
|
||||
var tooltipData = {};
|
||||
|
||||
var tootlipVisible = false;
|
||||
var tooltipEl = LN.$(`<div class="ln-tooltip"></div>`);
|
||||
|
||||
|
||||
|
||||
function findTooltipData(el){
|
||||
let tooltip = null;
|
||||
while (!tooltip)
|
||||
{
|
||||
tooltip = tooltipData[el.dataset['tooltip']];
|
||||
el = el.parentElement;
|
||||
}
|
||||
return tooltip;
|
||||
}
|
||||
|
||||
function tooltipShow(ev,tooltip){
|
||||
tooltipEl.innerText = tooltip.value;
|
||||
tooltipEl.setAttribute("VISIBLE","");
|
||||
|
||||
tootlipVisible = true;
|
||||
}
|
||||
function tooltipHide(ev,tooltip){
|
||||
tooltipEl.removeAttribute("VISIBLE");
|
||||
tootlipVisible = false;
|
||||
}
|
||||
|
||||
function tooltipMouseOver(ev){
|
||||
if (!document.body.contains(tooltipEl)){
|
||||
document.body.appendChild(tooltipEl);
|
||||
LN.$idle(()=>{ tooltipMouseOver(ev);});
|
||||
return;
|
||||
}
|
||||
|
||||
let tooltip = findTooltipData(ev.target);
|
||||
if (!tooltip)
|
||||
console.log(ev);
|
||||
|
||||
if (tooltipEl){
|
||||
tooltipEl.style.left = `${ev.pageX+3}px`;
|
||||
tooltipEl.style.top = `${ev.pageY+3}px`;
|
||||
}
|
||||
|
||||
if (tooltip.timeout)
|
||||
clearTimeout(tooltip.timeout);
|
||||
if (tootlipVisible){
|
||||
tooltip.timeout = setTimeout(() => {
|
||||
tooltipHide(ev,tooltip);
|
||||
}, (tooltip.delay ? (tooltip.delay / 4) : 200));
|
||||
} else {
|
||||
tooltip.timeout = setTimeout(() => {
|
||||
tooltipShow(ev,tooltip);
|
||||
}, tooltip.delay || 800);
|
||||
}
|
||||
}
|
||||
|
||||
function tooltipMouseOut(ev){
|
||||
let tooltip = findTooltipData(ev.target);
|
||||
if (tooltip.timeout)
|
||||
clearTimeout(tooltip.timeout);
|
||||
tooltipHide(ev,tooltip);
|
||||
}
|
||||
|
||||
Vue.directive('tooltip',{
|
||||
bind: function(el, binding, vnode){
|
||||
let tooltip = {
|
||||
value: binding.value,
|
||||
timeout: null,
|
||||
delay: null,
|
||||
};
|
||||
let tooltipKey = nextTooltipKey++;
|
||||
tooltipData[tooltipKey] = tooltip;
|
||||
el.dataset['tooltip'] = tooltipKey;
|
||||
|
||||
el.addEventListener('mousemove',tooltipMouseOver);
|
||||
el.addEventListener('mouseout',tooltipMouseOut);
|
||||
|
||||
},
|
||||
update: function(el, binding, vnode, oldVnode){
|
||||
let tooltip = findTooltipData(el);
|
||||
tooltip.value = binding.value;
|
||||
},
|
||||
unbind: function(el, binding, vnode){
|
||||
el.removeEventListener('mouseover',tooltipMouseOver);
|
||||
el.removeEventListener('mouseout',tooltipMouseOut);
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
Vue.component('ln-statusbar',{
|
||||
data: function(){
|
||||
return {
|
||||
current: LN.Promise.getCurrentPromises(),
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
CurrentPromises: function(){
|
||||
return this.current;
|
||||
}
|
||||
},
|
||||
template: `
|
||||
<div
|
||||
class="ln-statusbar"
|
||||
>
|
||||
<div>{{ LNVue.$_.statusText }}</div>
|
||||
<div style="flex-grow: 0;">
|
||||
<span
|
||||
v-if="!LNVue.$_.identity.UniqueID">NOT LOGGED IN
|
||||
</span>
|
||||
<span
|
||||
v-if="LNVue.$_.identity.UniqueID">{{ LNVue.$_.identity.IdentityName }}
|
||||
</span>
|
||||
</div>
|
||||
<div style="flex-grow: 0;">{{ LNVue.$_.socket && LNVue.$_.socket._state }}</div>
|
||||
<div
|
||||
class="ln-background-tasks"
|
||||
>{{ Object.keys(CurrentPromises).length || "No" }} Background Tasks
|
||||
<div
|
||||
v-if="Object.keys(CurrentPromises).length"
|
||||
>
|
||||
<div
|
||||
class="block"
|
||||
v-for="promise in CurrentPromises"
|
||||
:state="promise.state"
|
||||
>{{ promise.label }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div style="flex-grow: 0;">{{ LNVue.$_.Version() }}</div>
|
||||
</div>
|
||||
`,
|
||||
});
|
||||
|
||||
Vue.component('ln-navitem',{
|
||||
props: {
|
||||
value: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
key: String,
|
||||
},
|
||||
template: `
|
||||
<div
|
||||
class="ln-navitem"
|
||||
:id="'nav-' + key"
|
||||
>
|
||||
<router-link
|
||||
class="ln-navitem"
|
||||
v-if="value.path"
|
||||
:to="value.path"
|
||||
v-slot="{ href, route, navigate, isActive, isExactActive }"
|
||||
>
|
||||
<a
|
||||
:href="href"
|
||||
@click="navigate"
|
||||
>{{ value.label || value.path }}</a>
|
||||
</router-link>
|
||||
<span
|
||||
v-if="!value.path"
|
||||
class="ln-navitem"
|
||||
>{{ value.label }}</span>
|
||||
<div class="ln-nav-children"
|
||||
v-if="value.navigation"
|
||||
>
|
||||
<ln-navitem
|
||||
v-for="(item,key) in value.navigation"
|
||||
v-bind:value="item"
|
||||
v-bind:key="key"
|
||||
></ln-navitem>
|
||||
</div>
|
||||
</div>
|
||||
`,
|
||||
});
|
||||
|
||||
Vue.component('ln-navbar',{
|
||||
props: {
|
||||
value: {
|
||||
type: Object,
|
||||
default: function(){
|
||||
return LNVue.$_.navigation;
|
||||
}
|
||||
},
|
||||
component: {
|
||||
type: [ Object, String ],
|
||||
default: 'ln-navitem',
|
||||
},
|
||||
},
|
||||
template: `
|
||||
<div class="ln-navbar">
|
||||
<div
|
||||
:is="component"
|
||||
v-for="(item,key) in value"
|
||||
v-bind:value="item"
|
||||
v-bind:key="key"
|
||||
>{{ item.label || item.path }}</div>
|
||||
</div>`
|
||||
});
|
||||
|
||||
Vue.component('ln-textfield',{
|
||||
props: {
|
||||
value: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
template: `<input
|
||||
type="text"
|
||||
v-bind:value="value"
|
||||
v-on:input="$emit('input', $event.target.value)">`,
|
||||
});
|
||||
Vue.component('ln-password',{
|
||||
props: {
|
||||
value: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
template: `<input
|
||||
type="password"
|
||||
v-bind:value="value"
|
||||
autocomplete="current-password"
|
||||
v-on:input="$emit('input', $event.target.value)">`,
|
||||
});
|
||||
|
||||
Vue.component('ln-textarea',{
|
||||
props: {
|
||||
value: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
template: `<textarea v-on:input="$emit('input', $event.target.value)">{{ value }}</textarea>`,
|
||||
});
|
||||
|
||||
Vue.component('ln-number',{
|
||||
model: {
|
||||
prop: "value",
|
||||
event: "input",
|
||||
},
|
||||
props: {
|
||||
value: {
|
||||
required: false,
|
||||
type: Number,
|
||||
},
|
||||
float: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
|
||||
template: `
|
||||
<input
|
||||
type="number"
|
||||
v-bind:value="value"
|
||||
v-on:input="$emit('input', $event.target.value )"
|
||||
v-bind:step="float ? 'any' : 1"
|
||||
>`,
|
||||
});
|
||||
|
||||
Vue.component('ln-slider',{
|
||||
model: {
|
||||
prop: 'value',
|
||||
event: 'change',
|
||||
},
|
||||
props: {
|
||||
value: {
|
||||
type: Number,
|
||||
default: 1,
|
||||
},
|
||||
min: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
},
|
||||
max: {
|
||||
type: Number,
|
||||
default: 100,
|
||||
},
|
||||
step: {
|
||||
type: Number,
|
||||
default: 1,
|
||||
},
|
||||
islogarithmic: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
values: {
|
||||
default: null,
|
||||
}
|
||||
},
|
||||
data: function(){
|
||||
let d = {
|
||||
};
|
||||
|
||||
if (this.values instanceof Array){
|
||||
this.min = 0;
|
||||
this.max = this.values.length - 1;
|
||||
this.step = 1;
|
||||
} else if (this.values instanceof Object){
|
||||
this.min = 0;
|
||||
this.max = Object.keys(this.values).length - 1;
|
||||
this.step = 1;
|
||||
} else if (this.islogarithmic){
|
||||
/* ToDo: Implement */
|
||||
} else {
|
||||
|
||||
}
|
||||
|
||||
// console.log(`min=${this.min} max=${this.max} step=${this.step} value=${this.value}`);
|
||||
|
||||
return d;
|
||||
},
|
||||
computed: {
|
||||
keys: function(){
|
||||
if (this.values instanceof Array)
|
||||
{
|
||||
let keys = new Array(this.values.length);
|
||||
for (let n=0;n<keys.length;n++)
|
||||
keys[n] = n;
|
||||
return keys;
|
||||
} else if (this.values instanceof Object)
|
||||
{
|
||||
return Object.keys(this.values);
|
||||
}
|
||||
console.log("ln-slider.keys: ???");
|
||||
},
|
||||
displayValue: function(){
|
||||
if (this.values)
|
||||
{
|
||||
return this.values[this.value];
|
||||
}
|
||||
return this.mapFromRange(this.value);
|
||||
},
|
||||
sliderValue: {
|
||||
get: function(){ return this.mapToRange(this.value); },
|
||||
set: function(v){ this.value = this.mapFromRange(v); this.$emit('change', this.value); },
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
mapToRange: function(v){
|
||||
if (this.values)
|
||||
{
|
||||
if (this.values)
|
||||
{
|
||||
for (let idx=0;idx<this.keys.length;idx++)
|
||||
{
|
||||
if (v == this.keys[idx])
|
||||
{
|
||||
return idx;
|
||||
}
|
||||
}
|
||||
}
|
||||
console.log("slider position not found!");
|
||||
console.log(this.values);
|
||||
console.log(v);
|
||||
return 0;
|
||||
}
|
||||
return this.islogarithmic ? null : v;
|
||||
},
|
||||
mapFromRange: function(v){
|
||||
if (this.values)
|
||||
{
|
||||
return this.keys[v];
|
||||
}
|
||||
return this.islogarithmic ? null : v;
|
||||
},
|
||||
fireChange: function(){
|
||||
this.$emit('change',this.value);
|
||||
}
|
||||
},
|
||||
template: `
|
||||
<div class='ln-slider'>
|
||||
<input
|
||||
class="ln-slider"
|
||||
type="range"
|
||||
v-bind:min="min"
|
||||
v-bind:max="max"
|
||||
v-bind:step="step"
|
||||
v-model="sliderValue"
|
||||
@change="fireChange"
|
||||
>{{ displayValue }}
|
||||
</div>`,
|
||||
|
||||
});
|
||||
|
||||
Vue.component("ln-file",{
|
||||
props: {
|
||||
file: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
remove: {
|
||||
type: Function,
|
||||
required: false,
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
},
|
||||
template: `<div
|
||||
class="ln-upload-file"
|
||||
>{{ file.name }} ({{ file.size }}bytes)
|
||||
<sym
|
||||
@click="remove && remove(file)"
|
||||
class="trash"
|
||||
style="position: absolute; right: 2px;"
|
||||
></sym>
|
||||
</div>`
|
||||
});
|
||||
|
||||
Vue.component('ln-upload',{
|
||||
model: {
|
||||
prop: "files",
|
||||
event: "change",
|
||||
},
|
||||
props: {
|
||||
maxSize: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
},
|
||||
files: {
|
||||
type: Array,
|
||||
default: [],
|
||||
}
|
||||
},
|
||||
data: function(){ return { maxSize: this.maxSize, files: this.files, }; },
|
||||
methods: {
|
||||
drop: function(dropEvent){
|
||||
dropEvent.preventDefault();
|
||||
|
||||
for (let n=0;n<dropEvent.dataTransfer.files.length;n++)
|
||||
{
|
||||
//let file = dropEvent.dataTransfer.files[n];
|
||||
let item = dropEvent.dataTransfer.items[n];
|
||||
let file = item.getAsFile();
|
||||
|
||||
this.files.push({
|
||||
name: file.name,
|
||||
size: file.size,
|
||||
type: file.type,
|
||||
file: file,
|
||||
});
|
||||
}
|
||||
|
||||
this.$emit("change",this.files);
|
||||
console.log("files dropped...");
|
||||
},
|
||||
remove(file){
|
||||
console.log(file);
|
||||
let idx = this.files.indexOf(file);
|
||||
if (idx != -1)
|
||||
{
|
||||
this.files.splice(idx,1);
|
||||
this.$emit("change",this.files);
|
||||
}
|
||||
}
|
||||
},
|
||||
template: `
|
||||
<div
|
||||
class="ln-upload"
|
||||
@drop="drop"
|
||||
ondragover="event.preventDefault();"
|
||||
>Dateien hierher ziehen oder auswählen:<br>
|
||||
<input type="file" value=""><br>
|
||||
<ln-file
|
||||
v-for="file in files"
|
||||
v-bind:file="file"
|
||||
v-bind:remove="remove"
|
||||
></ln-file>
|
||||
</div>
|
||||
`,
|
||||
});
|
||||
|
||||
Vue.component('ln-select',{
|
||||
model: {
|
||||
prop: "value",
|
||||
event: "change",
|
||||
},
|
||||
props: {
|
||||
items: {
|
||||
required: true,
|
||||
},
|
||||
value: {
|
||||
required: true,
|
||||
},
|
||||
empty: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
render: {
|
||||
type: Function,
|
||||
default: function(value){
|
||||
return value;
|
||||
}
|
||||
},
|
||||
key: {
|
||||
type: Function,
|
||||
default: function(key,value){
|
||||
return key;
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
prepared: {
|
||||
get: function(){
|
||||
let prepared = {};
|
||||
LN.$each(this.items,(element,index) => {
|
||||
prepared[index] = element;
|
||||
});
|
||||
return prepared;
|
||||
},
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
changed: function(ev){
|
||||
this.$emit("change", ev.target.value );
|
||||
},
|
||||
},
|
||||
|
||||
template: `<div class="ln-select">
|
||||
<select @change="changed">
|
||||
<option
|
||||
v-if="empty"
|
||||
value=""
|
||||
></option>
|
||||
<option
|
||||
v-for="(item,_key) in prepared"
|
||||
v-bind:value="key(_key,item)"
|
||||
:selected="_key == value"
|
||||
>{{ render(item) }}</option>
|
||||
</select>
|
||||
</div>
|
||||
`,
|
||||
});
|
||||
|
||||
Vue.component('ln-identity',{
|
||||
data: function(){
|
||||
return {
|
||||
popupVisible: false,
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
logout(){
|
||||
LNVue.$_.authenticate("",null,"","");
|
||||
this.popupVisible = false;
|
||||
},
|
||||
popup(){
|
||||
this.popupVisible = !this.popupVisible;
|
||||
},
|
||||
},
|
||||
template: `<div class="ln-identity">
|
||||
<div
|
||||
v-if="LNVue.$_.identity.UniqueID"
|
||||
v-tooltip="LNVue.$_.identity.UniqueID"
|
||||
>
|
||||
{{ LNVue.$_.identity.IdentityName }}
|
||||
</div>
|
||||
<div
|
||||
v-if="!LNVue.$_.identity.UniqueID"
|
||||
@click="popup()"
|
||||
>Not logged in
|
||||
</div>
|
||||
<button
|
||||
class="no-border fa-solid"
|
||||
@click="popup();"
|
||||
></button>
|
||||
<div
|
||||
class="popup"
|
||||
v-if="popupVisible"
|
||||
>
|
||||
<div
|
||||
v-if="LNVue.$_.identity.UniqueID"
|
||||
><button
|
||||
@click="logout()"
|
||||
>Logout</button>
|
||||
</div>
|
||||
<div
|
||||
v-if="!LNVue.$_.identity.UniqueID"
|
||||
is="ln-login-pane"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</div>`,
|
||||
});
|
||||
|
||||
Vue.component('ln-popup',{
|
||||
props: {
|
||||
title: String,
|
||||
visible: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
show: function(){
|
||||
this.visible = true;
|
||||
},
|
||||
hide: function(){
|
||||
this.visible = false;
|
||||
}
|
||||
},
|
||||
template: `<div class="ln-popup">
|
||||
<div
|
||||
class="ln-popup-frame"
|
||||
><div
|
||||
class="ln-popup-title"
|
||||
v-if="title"
|
||||
>{{ title }}</div>
|
||||
<div class="ln-popup-content">
|
||||
<slot></slot>
|
||||
</div>
|
||||
<div
|
||||
class="ln-popup-footer"
|
||||
>
|
||||
<button
|
||||
@click="hide()"
|
||||
>schliessen</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`,
|
||||
});
|
||||
|
||||
Vue.component('ln-login-pane',{
|
||||
props: {
|
||||
},
|
||||
methods: {
|
||||
stage1: function(){
|
||||
console.log("login stage1", this.identityName);
|
||||
|
||||
LNVue.$_
|
||||
.requestChallenges(this.identityName)
|
||||
.then((challenges)=>{
|
||||
challenges = challenges.message.Challenges;
|
||||
if (challenges.length > 0){
|
||||
LNVue.$_.identity.IdentityName = this.identityName;
|
||||
LNVue.$_.identity.challenges = challenges;
|
||||
}
|
||||
});
|
||||
},
|
||||
logout(){
|
||||
LNVue.$_.authenticate("",null,"","");
|
||||
},
|
||||
authenticateSeededPassword(challenge){
|
||||
let encoder = new TextEncoder();
|
||||
let seed = LNVue.decodeHex(challenge.AuthenticationParameters);
|
||||
let password = encoder.encode(challenge.prove);
|
||||
|
||||
let secretSource = ArrayBuffer.combine(seed, password, seed);
|
||||
|
||||
crypto.subtle.digest("SHA-256",secretSource)
|
||||
.then((secret)=>{
|
||||
let challengebytes = LN.Base64.decode(challenge.Challenge);
|
||||
secret = new Uint8Array(secret);
|
||||
|
||||
let proveSource = ArrayBuffer.combine(challengebytes, secret, challengebytes);
|
||||
|
||||
crypto.subtle.digest("SHA-256",proveSource)
|
||||
.then((prove)=>{
|
||||
prove = LN.Base64.encode(new Uint8Array(prove));
|
||||
LNVue.$_.authenticate(this.identityName,challenge.SecureAttributeID,challenge.Challenge,prove);
|
||||
});
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
},
|
||||
data: ()=>{
|
||||
return {
|
||||
identityName: "",
|
||||
};
|
||||
},
|
||||
template: `<div>
|
||||
<div
|
||||
v-if="LNVue.$_.identity.UniqueID"
|
||||
>
|
||||
<div>Logged in as</div>
|
||||
{{ LNVue.$_.identity.IdentityName }}
|
||||
<button @click="logout()">Logout</button>
|
||||
</div>
|
||||
<div
|
||||
v-if="!LNVue.$_.identity.UniqueID">
|
||||
<div
|
||||
v-if="LNVue.$_.identity.IdentityName == ''"
|
||||
><form @submit.prevent="stage1();">
|
||||
<label for="username">Username</label>
|
||||
<ln-textfield
|
||||
name="username"
|
||||
autocomplete="username"
|
||||
v-model="identityName"></ln-textfield>
|
||||
<button
|
||||
:disabled="identityName == ''"
|
||||
type="submit"
|
||||
>continue</button>
|
||||
</form>
|
||||
</div>
|
||||
<div
|
||||
v-if="LNVue.$_.identity.IdentityName != ''"
|
||||
>
|
||||
<div
|
||||
v-for="challenge in LNVue.$_.identity.challenges"
|
||||
>
|
||||
<form @submit.prevent="authenticateSeededPassword(challenge); return false;">
|
||||
<input style="display: none;" type="text" name="username" autocomplete="username" :value="LNVue.$_.identity.IdentityName">
|
||||
<div
|
||||
v-if="challenge.SecureAttributeTypeName == 'SeededPassword'"
|
||||
>{{ challenge.SecureAttributeLabel }}
|
||||
<ln-password
|
||||
v-model="challenge.prove"
|
||||
></ln-password>
|
||||
<button
|
||||
type="submit"
|
||||
:disabled="challenge.prove==''"
|
||||
>login</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>`,
|
||||
});
|
||||
|
||||
|
||||
})();
|
|
@ -0,0 +1,274 @@
|
|||
(function (){
|
||||
|
||||
let exModule = {
|
||||
label: 'Example Module',
|
||||
routes: [
|
||||
{
|
||||
path: '/about',
|
||||
template: `This is about this example!`,
|
||||
}
|
||||
],
|
||||
navigation: {
|
||||
'99': {
|
||||
label: 'About',
|
||||
href: '/about'
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
};
|
||||
|
||||
let defaultOptions = {
|
||||
element: 'body',
|
||||
modules: [ exModule, ],
|
||||
};
|
||||
|
||||
class LNVue
|
||||
{
|
||||
constructor(el,options = {}){
|
||||
this.options = Object.assign({
|
||||
routes: [],
|
||||
data: {},
|
||||
}, options );
|
||||
|
||||
this._el = el;
|
||||
this.data = Object.assign({}, options.data, { LNVue: this, msg: "Hello World" });
|
||||
this.promises = [];
|
||||
|
||||
this.statusText = "LNVue preparing";
|
||||
|
||||
Vue.prototype.$LNVue = this;
|
||||
LNVue.$_ = this;
|
||||
|
||||
this.navigation = {};
|
||||
this.identity = new LN.Identity();
|
||||
|
||||
Promise
|
||||
.all(LNVue.promises)
|
||||
.then(()=>{
|
||||
this.status("LNVue: starting");
|
||||
|
||||
LNVue.vueRouter.addRoutes([{
|
||||
path: "*",
|
||||
component: {
|
||||
template: `<h2>404 Not Found</h2>The URL you tried to reach is not existing.`,
|
||||
},
|
||||
}]);
|
||||
},
|
||||
(cause)=>{
|
||||
this.status("LNVue: start failed: " + cause);
|
||||
});
|
||||
|
||||
this.vue = null;
|
||||
|
||||
LNVue.$instance.resolve(this);
|
||||
}
|
||||
|
||||
Start(){
|
||||
Promise
|
||||
.all(this.promises)
|
||||
.then(()=>{
|
||||
LN.$idle(()=>{
|
||||
this.vue = new Vue({
|
||||
el: this._el,
|
||||
data: this.data,
|
||||
router: LNVue.vueRouter,
|
||||
});
|
||||
});
|
||||
});
|
||||
LN.$idle(()=>{
|
||||
this.socket = new LN.Vue.WebSocket(this);
|
||||
this.socket.open();
|
||||
});
|
||||
LN.$idle(()=>{
|
||||
LNVue.$start.resolve(this);
|
||||
});
|
||||
}
|
||||
|
||||
storage(){
|
||||
return window.localStorage;
|
||||
}
|
||||
|
||||
sessionID(){
|
||||
if (arguments.length == 1){
|
||||
this.storage().setItem("LNVueSessionID",arguments[0]);
|
||||
console.log("LNVue.SID <= " + arguments[0]);
|
||||
return this;
|
||||
} else
|
||||
{
|
||||
let sid = this.storage().getItem("LNVueSessionID");
|
||||
console.log("LNVue.SID == " + sid);
|
||||
if (!sid)
|
||||
{
|
||||
sid = "00000000-0000-0000-0000-000000000000";
|
||||
}
|
||||
return sid;
|
||||
}
|
||||
}
|
||||
|
||||
Version(){ return "0.2alpha"; };
|
||||
getCurrentPromises() {
|
||||
return LN.Promise.getCurrentPromises();
|
||||
}
|
||||
|
||||
status(){
|
||||
if (arguments.length == 1){
|
||||
this.statusText = arguments[0];
|
||||
return this;
|
||||
} else if (arguments.length == 0){
|
||||
return this.statusText;
|
||||
} else
|
||||
throw "LNVue.status(): too many arguments";
|
||||
}
|
||||
|
||||
addModule(modSpec){
|
||||
if (modSpec.navigation instanceof Object){
|
||||
LNVue.deepAssign(modSpec.navigation,this.navigation);
|
||||
}
|
||||
|
||||
LN.$each(modSpec.routes,(key,route)=>{
|
||||
if ((route instanceof Object) && route.url)
|
||||
{
|
||||
let p = new LN.Promise((resolve,reject)=>{
|
||||
LN.$fetch(route.url)
|
||||
.then((src)=>{
|
||||
this.addRoute(key,{ template: src, data: ()=>{ return this.data; }, });
|
||||
resolve();
|
||||
},
|
||||
(cause)=>{
|
||||
console.log("loading route.url failed: ",cause);
|
||||
});
|
||||
},`addModule(${route.url})`);
|
||||
this.promises.push(p);
|
||||
} else if (route instanceof Object){
|
||||
this.addRoute(key,{ template: route.template, data: ()=>{ return this.data; }, } );
|
||||
} else {
|
||||
this.addRoute(key,{ template: route, data: ()=>{ return this.data; }, } );
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
addRoute(path,component){
|
||||
LNVue.vueRouter.addRoutes([
|
||||
{ path, component, },
|
||||
]);
|
||||
|
||||
if (this.vue){
|
||||
let route = this.vue.$route;
|
||||
LNVue.vueRouter.replace("/");
|
||||
LNVue.vueRouter.replace(route);
|
||||
}
|
||||
}
|
||||
|
||||
/* Authentication API */
|
||||
|
||||
requestChallenges(identityName,secureAttributeTypeName){
|
||||
return new Promise((resolve,reject)=>{
|
||||
this.socket.request("AuthenticationRequest",{
|
||||
IdentityName: identityName,
|
||||
SecureAttributeTypeName: secureAttributeTypeName,
|
||||
})
|
||||
.then((challenges)=>{
|
||||
resolve(challenges);
|
||||
},
|
||||
(error)=>{
|
||||
console.log("Login challenges could not be retrieved", error);
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
authenticate(identityName,secureAttributeID,challenge,prove){
|
||||
let authenticationProve = {
|
||||
IdentityName: identityName,
|
||||
SecureAttributeUniqueID: secureAttributeID,
|
||||
Challenge: challenge,
|
||||
Prove: prove,
|
||||
};
|
||||
this.socket.request("AuthenticationProve", authenticationProve)
|
||||
.then((identity)=>{
|
||||
this.identity = new LN.Identity(identity.message);
|
||||
},
|
||||
(error)=>{
|
||||
this.identity = new LN.Identity();
|
||||
});
|
||||
}
|
||||
|
||||
rpc(moduleName,methodName,parameters){
|
||||
return new Promise((resolve,reject)=>{
|
||||
let rpcCall = {
|
||||
module: moduleName,
|
||||
method: methodName,
|
||||
parameters: parameters
|
||||
};
|
||||
this.socket.request("RPCCall",rpcCall)
|
||||
.then(
|
||||
(result)=>{
|
||||
if (result.message.error)
|
||||
{
|
||||
console.log("rpc call failed", result.message.error);
|
||||
reject(result.message.error);
|
||||
}
|
||||
else
|
||||
resolve(result.message.Result);
|
||||
},
|
||||
(error)=>{
|
||||
console.log("rpc failed", error);
|
||||
reject(error);
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
static $LNVue(){
|
||||
return LNVue.$_;
|
||||
}
|
||||
}
|
||||
LNVue.$instance = new LN.Promise(()=>{},'LN.Vue Instance Promise');
|
||||
LNVue.$start = new LN.Promise(()=>{},'LN Vue Startup Promise');
|
||||
|
||||
|
||||
|
||||
LNVue.vueRouter = new VueRouter({
|
||||
mode: 'history',
|
||||
routes: [],
|
||||
});
|
||||
|
||||
LNVue.deepAssign = function(source,target){
|
||||
LN.$each(source,function(key){
|
||||
if (target[key] instanceof Object){
|
||||
LNVue.deepAssign(src[key],target[key]);
|
||||
} else {
|
||||
target[key] = source[key];
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
LNVue.routes = [];
|
||||
LNVue.promises = [];
|
||||
|
||||
LNVue.encodeHex = (bytes) => bytes.reduce((str, byte) => str + byte.toString(16).padStart(2, '0'), '');
|
||||
LNVue.decodeHex = (hexString) => new Uint8Array(hexString.match(/.{1,2}/g).map(byte => parseInt(byte, 16)));
|
||||
|
||||
ArrayBuffer.combine = function(...args){
|
||||
let byteLength = 0;
|
||||
args.forEach((arg,index)=>{
|
||||
byteLength = byteLength + arg.byteLength;
|
||||
});
|
||||
|
||||
let result = new Uint8Array(byteLength);
|
||||
let p = 0;
|
||||
args.forEach((arg,index)=>{
|
||||
for (let n=0;n<arg.byteLength;n++)
|
||||
{
|
||||
result[p++] = arg[n];
|
||||
}
|
||||
});
|
||||
console.log("combine",new Uint8Array(result));
|
||||
return result.buffer
|
||||
};
|
||||
|
||||
LN.$add("LN.Vue",LNVue);
|
||||
LN.$add("LNVue",LNVue);
|
||||
})();
|
|
@ -0,0 +1,45 @@
|
|||
(function(){
|
||||
|
||||
// LNVue.prototype.createCredentials = function(_challenge,rp,userId,crossplatform,attestation){
|
||||
// crossplatform = crossplatform ? true : false;
|
||||
// if (!attestation)
|
||||
// attestation = "none";
|
||||
|
||||
// let credCreateOptions = {
|
||||
// challenge: Uint8Array.from(_challenge, c => c.charCodeAt(0)),
|
||||
// rp: {
|
||||
// id: rp,
|
||||
// name: 'LN.Vue Demo Application',
|
||||
// },
|
||||
// user: {
|
||||
// id: Int8Array.from(userId, c => c.charCodeAt(0)),
|
||||
// name: 'SomeUser',
|
||||
// displayName: "Some user for now...",
|
||||
// },
|
||||
// pubKeyCredParams: [
|
||||
// {
|
||||
// type: 'public-key',
|
||||
// alg: -7,
|
||||
// }
|
||||
// ],
|
||||
// authenticatorSelection: {
|
||||
// authenticatorAttachment: crossplatform ? 'cross-platform' : 'platform',
|
||||
// },
|
||||
// timeout: 60000,
|
||||
// attestation: attestation,
|
||||
// };
|
||||
|
||||
// console.log(credCreateOptions);
|
||||
// navigator.credentials.create({
|
||||
// publicKey: credCreateOptions
|
||||
// });
|
||||
|
||||
// };
|
||||
|
||||
// LNVue.prototype.createAuthToken = function(){
|
||||
|
||||
// };
|
||||
|
||||
|
||||
|
||||
})();
|
|
@ -0,0 +1,137 @@
|
|||
(function(){
|
||||
class LNVueWebSocket
|
||||
{
|
||||
constructor(lnvue,o){
|
||||
this.LNVue = lnvue;
|
||||
this.options = Object.assign({},o);
|
||||
|
||||
if (!this.options.url)
|
||||
this.options.url = this.constructURL();
|
||||
|
||||
this._id = 1;
|
||||
this.defaultTimeout = 30000;
|
||||
this.websocket = null;
|
||||
this.callbacks = {};
|
||||
|
||||
this._state = "initialized";
|
||||
this._retry = null;
|
||||
|
||||
this.closing = false;
|
||||
}
|
||||
|
||||
constructURL(){
|
||||
var pageURI = window.location;
|
||||
|
||||
var scheme = pageURI.scheme == "https" ? "wss:" : "ws:";
|
||||
var host = pageURI.host;
|
||||
|
||||
return scheme + "//" + host + "/socket";
|
||||
}
|
||||
|
||||
open(){
|
||||
if (this._retry){
|
||||
clearTimeout(this._retry);
|
||||
}
|
||||
this.closing = false;
|
||||
|
||||
this.websocket = new WebSocket(this.options.url);
|
||||
this.websocket.onopen = (e) =>{
|
||||
console.log("WebSocket connected");
|
||||
this._state = "ONLINE";
|
||||
|
||||
let WSHello = {
|
||||
ApplicationSessionID: this.LNVue.sessionID(),
|
||||
};
|
||||
console.log("WSHello request",WSHello);
|
||||
|
||||
this.request("WSHello",WSHello)
|
||||
.then((wsh)=>{
|
||||
console.log("WSHello response",wsh);
|
||||
this.LNVue.sessionID(wsh.message.ApplicationSessionID);
|
||||
this.LNVue.identity = new LN.Identity(wsh.message.SessionIdentity);
|
||||
});
|
||||
};
|
||||
this.websocket.onclose = (e)=>{ this._onclose(e); this._state = "OFFLINE"; };
|
||||
this.websocket.onerror = (e)=>{ this._onerror(e); this._state = "ERROR"; };
|
||||
this.websocket.onmessage = (e)=>{ this._onmessage(e); };
|
||||
return this;
|
||||
}
|
||||
|
||||
close(){
|
||||
if (this.websocket){
|
||||
this.closing = true;
|
||||
this.websocket.close(200,"close() called");
|
||||
}
|
||||
}
|
||||
|
||||
request(msgtype,msg,timeout){
|
||||
let message = {
|
||||
id: this._id++,
|
||||
type: msgtype,
|
||||
message: msg,
|
||||
}
|
||||
|
||||
if (!timeout)
|
||||
timeout = this.defaultTimeout;
|
||||
|
||||
if (timeout != -1){
|
||||
return new Promise((resolve,reject)=>{
|
||||
let to = setTimeout(()=>{
|
||||
delete this.callbacks[message.id];
|
||||
reject("timed out");
|
||||
},timeout);
|
||||
|
||||
this.callbacks[message.id] = (msgtype,msg)=>{
|
||||
clearTimeout(to);
|
||||
delete this.callbacks[message.id];
|
||||
if (msgtype == "error")
|
||||
reject(msg);
|
||||
else
|
||||
resolve({type: msgtype,message: msg});
|
||||
};
|
||||
|
||||
this.websocket.send(
|
||||
JSON.stringify(message)
|
||||
);
|
||||
});
|
||||
} else {
|
||||
new Promise((resolve,reject)=>{
|
||||
this.websocket.send(
|
||||
JSON.stringify(message)
|
||||
);
|
||||
resolve();
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
_onclose(evt){
|
||||
this.websocket = null;
|
||||
this.options.onclose && this.options.onclose(evt);
|
||||
if (!this.closing)
|
||||
{
|
||||
this._retry = setTimeout(() => {
|
||||
this._retry = null;
|
||||
console.log("reconnect...")
|
||||
this.open();
|
||||
}, 5000);
|
||||
}
|
||||
}
|
||||
_onerror(evt){
|
||||
this.options.onerror && this.options.onerror(evt);
|
||||
}
|
||||
_onmessage(evt){
|
||||
try
|
||||
{
|
||||
let j = JSON.parse(evt.data);
|
||||
let cb = this.callbacks[ j.id ];
|
||||
cb && cb(j.type,j.message);
|
||||
} catch(exc){
|
||||
console.log(exc,evt.data);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
LN.$add("LN.Vue.WebSocket",LNVueWebSocket);
|
||||
})();
|
|
@ -0,0 +1,107 @@
|
|||
(function(globals){
|
||||
let _unique = new Date().getTime();
|
||||
let __idles__ = [];
|
||||
|
||||
class LN
|
||||
{
|
||||
constructor(opts){
|
||||
}
|
||||
|
||||
static $each(oa,cb){
|
||||
if (oa instanceof Array){
|
||||
let result = [];
|
||||
oa.forEach((value,index)=>{
|
||||
result.push(cb.call(value,index,value));
|
||||
});
|
||||
return result;
|
||||
} else if (oa instanceof Object){
|
||||
let result = {};
|
||||
Object.keys(oa).forEach((key)=>{
|
||||
if (oa.hasOwnProperty(key)){
|
||||
result[key] = cb.call(oa[key],key,oa[key]);
|
||||
}
|
||||
});
|
||||
return result;
|
||||
}
|
||||
}
|
||||
static $idle(cb,thisval = null){
|
||||
let scheduled = __idles__.length > 0;
|
||||
let n=0;
|
||||
|
||||
for (;n<__idles__.length;n++){
|
||||
let idle = __idles__[n];
|
||||
if ((idle[0] == cb) && (idle[1] == thisval))
|
||||
break;
|
||||
}
|
||||
if (n == __idles__.length)
|
||||
__idles__.push([cb,thisval]);
|
||||
|
||||
if (!scheduled)
|
||||
setTimeout(()=>{
|
||||
while (__idles__.length > 0){
|
||||
let idle = __idles__.pop();
|
||||
idle[0].call(idle[1]);
|
||||
}
|
||||
},0);
|
||||
}
|
||||
static $unique(){
|
||||
return _unique++;
|
||||
}
|
||||
static $fetch(url,cb){
|
||||
return new LN.Promise((resolve,reject)=>{
|
||||
fetch(url)
|
||||
.then((response => {
|
||||
if (response.status.toString().startsWith("2"))
|
||||
{
|
||||
let t = response.text();
|
||||
cb && cb(t,null);
|
||||
resolve(t);
|
||||
} else {
|
||||
cb && cb(null, response.statusText);
|
||||
reject(response.statusText);
|
||||
}
|
||||
}));
|
||||
}, `fetch(${url})`);
|
||||
}
|
||||
|
||||
static $resolve(v){
|
||||
if (v instanceof Function)
|
||||
return v();
|
||||
return v;
|
||||
}
|
||||
|
||||
static $add(classpath,c){
|
||||
let p = classpath.split(".");
|
||||
if (p.length < 1)
|
||||
throw "invalid classpath";
|
||||
|
||||
let container = globals;
|
||||
while (p.length > 1){
|
||||
let next = p.shift();
|
||||
if (!container[next])
|
||||
container[next] = {}
|
||||
container = container[next];
|
||||
}
|
||||
|
||||
let prev = container[p[0]];
|
||||
container[p[0]] = c;
|
||||
|
||||
if (prev)
|
||||
{
|
||||
LN.$each(prev,(key,value)=>{
|
||||
c[key] = value;
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
static $(selector){
|
||||
let el = document.createElement("parse");
|
||||
el.innerHTML = selector;
|
||||
return el.firstChild;
|
||||
}
|
||||
}
|
||||
LN.prototypes = {};
|
||||
|
||||
LN.$add("LN", LN);
|
||||
})(window);
|
|
@ -1,121 +0,0 @@
|
|||
var LNVue = (function (){
|
||||
|
||||
|
||||
class LNVue
|
||||
{
|
||||
constructor(el,options = {}){
|
||||
this.options = Object.assign({
|
||||
routes: [],
|
||||
data: {},
|
||||
}, options );
|
||||
this.data = Object.assign({}, options.data, { LNVue: this, msg: "Hello World" });
|
||||
|
||||
Vue.prototype.$LNVue = this;
|
||||
|
||||
console.log("LNVue: preparing");
|
||||
|
||||
Promise
|
||||
.all(LNVue.promises)
|
||||
.then(()=>{
|
||||
console.log("LNVue: starting");
|
||||
|
||||
let routes = LNVue.routes
|
||||
.concat(this.options.routes)
|
||||
.concat([{
|
||||
path: "*",
|
||||
component: {
|
||||
template: `<h2>404 Not Found</h2>The URL you tried to reach is not existing.`,
|
||||
},
|
||||
}]);
|
||||
|
||||
let router = new VueRouter({
|
||||
routes,
|
||||
});
|
||||
|
||||
let vue = new Vue({
|
||||
el,
|
||||
data: this.data,
|
||||
router: router,
|
||||
});
|
||||
|
||||
this.vue = function(){ return vue; }
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
Version(){ return "0.1alpha"; };
|
||||
|
||||
}
|
||||
|
||||
LNVue.$ = function(src){
|
||||
let el = document.createElement("parse");
|
||||
el.innerHTML = src;
|
||||
return el.firstChild;
|
||||
}
|
||||
|
||||
LNVue.routes = [];
|
||||
LNVue.promises = [];
|
||||
|
||||
LNVue.fetch = function(url,cb){
|
||||
let self = this;
|
||||
return new Promise(function(resolve,reject){
|
||||
fetch(url)
|
||||
.then((response => {
|
||||
if (response.status.toString().startsWith("2"))
|
||||
{
|
||||
let t = response.text();
|
||||
cb && cb(t,null);
|
||||
resolve(t);
|
||||
} else {
|
||||
cb && cb(null, response.statusText);
|
||||
reject(response.statusText);
|
||||
}
|
||||
}));
|
||||
});
|
||||
}
|
||||
|
||||
LNVue.each = function(o,cb){
|
||||
if (o instanceof Array)
|
||||
o.forEach((value,index)=> cb(value,index) );
|
||||
else
|
||||
Object.keys(o).forEach((key)=>{
|
||||
if (o.hasOwnProperty(key))
|
||||
cb(o[key],key);
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
LNVue.addRouteTemplate = function(path,template){
|
||||
LNVue.routes.push({
|
||||
path,
|
||||
component: {
|
||||
data: function(){
|
||||
return this.$LNVue.data;
|
||||
},
|
||||
template,
|
||||
}
|
||||
});
|
||||
}
|
||||
LNVue.addRoute = function(path,url){
|
||||
let p = new Promise((resolve,reject) => {
|
||||
LNVue
|
||||
.fetch(url)
|
||||
.then((src)=>{
|
||||
LNVue.routes.push({
|
||||
path,
|
||||
component: {
|
||||
data: function(){
|
||||
return this.$LNVue.data;
|
||||
},
|
||||
template: src,
|
||||
},
|
||||
});
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
LNVue.promises.push(p);
|
||||
return p;
|
||||
}
|
||||
|
||||
return LNVue;
|
||||
})();
|
|
@ -1,450 +0,0 @@
|
|||
(function(){
|
||||
|
||||
let nextTooltipKey = 0;
|
||||
var tooltipData = {};
|
||||
|
||||
var tootlipVisible = false;
|
||||
var tooltipEl = LNVue.$(`<div class="ln-tooltip"></div>`);
|
||||
|
||||
|
||||
function findTooltipData(el){
|
||||
let tooltip = null;
|
||||
while (!tooltip)
|
||||
{
|
||||
tooltip = tooltipData[el.dataset['tooltip']];
|
||||
el = el.parentElement;
|
||||
}
|
||||
return tooltip;
|
||||
}
|
||||
|
||||
function tooltipShow(ev,tooltip){
|
||||
if (!tooltipEl.parentElement)
|
||||
{
|
||||
tooltipEl.style.visibility = "hidden";
|
||||
tooltipEl.style.opacity = 0.0;
|
||||
tooltipEl.style.visibility = "visible";
|
||||
}
|
||||
document.body.appendChild(tooltipEl);
|
||||
|
||||
tooltipEl.innerText = tooltip.value;
|
||||
tooltipEl.style.opacity = 1.0;
|
||||
|
||||
tootlipVisible = true;
|
||||
|
||||
}
|
||||
function tooltipHide(ev,tooltip){
|
||||
tooltipEl.style.opacity = 0.0;
|
||||
setTimeout(()=>{
|
||||
document.body.removeChild(tooltipEl);
|
||||
}, 600);
|
||||
|
||||
tootlipVisible = false;
|
||||
}
|
||||
|
||||
function tooltipMouseOver(ev){
|
||||
let tooltip = findTooltipData(ev.target);
|
||||
if (!tooltip)
|
||||
console.log(ev);
|
||||
if (tooltip.timeout)
|
||||
clearTimeout(tooltip.timeout);
|
||||
if (tootlipVisible)
|
||||
tooltipHide(ev,tooltip);
|
||||
else
|
||||
tooltip.timeout = setTimeout(() => {
|
||||
tooltipShow(ev,tooltip);
|
||||
}, 800);
|
||||
|
||||
if (tooltipEl){
|
||||
tooltipEl.style.left = `${ev.x+3}px`;
|
||||
tooltipEl.style.top = `${ev.y+3}px`;
|
||||
}
|
||||
}
|
||||
|
||||
function tooltipMouseOut(ev){
|
||||
let tooltip = findTooltipData(ev.target);
|
||||
if (tooltip.timeout)
|
||||
clearTimeout(tooltip.timeout);
|
||||
tooltipHide(ev,tooltip);
|
||||
}
|
||||
|
||||
Vue.directive('tooltip',{
|
||||
bind: function(el, binding, vnode){
|
||||
let tooltip = {
|
||||
value: binding.value,
|
||||
timeout: null,
|
||||
|
||||
};
|
||||
let tooltipKey = nextTooltipKey++;
|
||||
tooltipData[tooltipKey] = tooltip;
|
||||
el.dataset['tooltip'] = tooltipKey;
|
||||
|
||||
el.addEventListener('mousemove',tooltipMouseOver);
|
||||
el.addEventListener('mouseout',tooltipMouseOut);
|
||||
|
||||
},
|
||||
inserted: function(el, binding, vnode){
|
||||
|
||||
},
|
||||
update: function(el, binding, vnode, oldVnode){
|
||||
|
||||
},
|
||||
componentUpdated: function(el, binding, vnode, oldVnode){
|
||||
|
||||
},
|
||||
unbind: function(el, binding, vnode){
|
||||
el.removeEventListener('mouseover',tooltipMouseOver);
|
||||
el.removeEventListener('mouseout',tooltipMouseOut);
|
||||
},
|
||||
});
|
||||
|
||||
Vue.component('ln-navitem',{
|
||||
props: {
|
||||
href: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
template: `
|
||||
<router-link
|
||||
:to="href"
|
||||
class="ln-navitem"unbind
|
||||
v-slot="{ href, route, navigate, isActive, isExactActive }"
|
||||
>
|
||||
<div class="ln-navitem">
|
||||
<a :href="href" @click="navigate">
|
||||
<slot></slot>
|
||||
</a>
|
||||
<slot name="children"></slot>
|
||||
</div>
|
||||
</router-link>
|
||||
`,
|
||||
});
|
||||
|
||||
Vue.component('ln-navbar',{
|
||||
props: {
|
||||
navitems: {
|
||||
type: Object,
|
||||
required: true,
|
||||
}
|
||||
},
|
||||
template: `
|
||||
<div class="ln-navbar">
|
||||
<ln-navitem
|
||||
v-for="navitem in navitems"
|
||||
v-bind:href="navitem.href"
|
||||
>{{ navitem.label || navitem.href }}</ln-navitem>
|
||||
</div>`
|
||||
});
|
||||
|
||||
Vue.component('ln-textfield',{
|
||||
props: {
|
||||
value: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
template: `<input
|
||||
type="text"
|
||||
v-bind:value="value"
|
||||
v-on:input="$emit('input', $event.target.value)">`,
|
||||
});
|
||||
|
||||
Vue.component('ln-textarea',{
|
||||
props: {
|
||||
value: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
template: `<textarea v-on:input="$emit('input', $event.target.value)">{{ value }}</textarea>`,
|
||||
});
|
||||
|
||||
Vue.component('ln-number',{
|
||||
model: {
|
||||
prop: "value",
|
||||
event: "input",
|
||||
},
|
||||
props: {
|
||||
value: {
|
||||
required: false,
|
||||
type: Number,
|
||||
},
|
||||
float: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
|
||||
template: `
|
||||
<input
|
||||
type="number"
|
||||
v-bind:value="value"
|
||||
v-on:input="$emit('input', $event.target.value )"
|
||||
v-bind:step="float ? 'any' : 1"
|
||||
>`,
|
||||
});
|
||||
|
||||
Vue.component('ln-slider',{
|
||||
model: {
|
||||
prop: 'value',
|
||||
event: 'change',
|
||||
},
|
||||
props: {
|
||||
value: {
|
||||
type: Number,
|
||||
default: 1,
|
||||
},
|
||||
min: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
},
|
||||
max: {
|
||||
type: Number,
|
||||
default: 100,
|
||||
},
|
||||
step: {
|
||||
type: Number,
|
||||
default: 1,
|
||||
},
|
||||
islogarithmic: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
values: {
|
||||
default: null,
|
||||
}
|
||||
},
|
||||
data: function(){
|
||||
let d = {
|
||||
};
|
||||
|
||||
if (this.values instanceof Array){
|
||||
this.min = 0;
|
||||
this.max = this.values.length - 1;
|
||||
this.step = 1;
|
||||
} else if (this.values instanceof Object){
|
||||
this.min = 0;
|
||||
this.max = Object.keys(this.values).length - 1;
|
||||
this.step = 1;
|
||||
} else if (this.islogarithmic){
|
||||
/* ToDo: Implement */
|
||||
} else {
|
||||
|
||||
}
|
||||
|
||||
// console.log(`min=${this.min} max=${this.max} step=${this.step} value=${this.value}`);
|
||||
|
||||
return d;
|
||||
},
|
||||
computed: {
|
||||
keys: function(){
|
||||
if (this.values instanceof Array)
|
||||
{
|
||||
let keys = new Array(this.values.length);
|
||||
for (let n=0;n<keys.length;n++)
|
||||
keys[n] = n;
|
||||
return keys;
|
||||
} else if (this.values instanceof Object)
|
||||
{
|
||||
return Object.keys(this.values);
|
||||
}
|
||||
console.log("ln-slider.keys: ???");
|
||||
},
|
||||
displayValue: function(){
|
||||
if (this.values)
|
||||
{
|
||||
return this.values[this.value];
|
||||
}
|
||||
return this.mapFromRange(this.value);
|
||||
},
|
||||
sliderValue: {
|
||||
get: function(){ return this.mapToRange(this.value); },
|
||||
set: function(v){ this.value = this.mapFromRange(v); this.$emit('change', this.value); },
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
mapToRange: function(v){
|
||||
if (this.values)
|
||||
{
|
||||
if (this.values)
|
||||
{
|
||||
for (let idx=0;idx<this.keys.length;idx++)
|
||||
{
|
||||
if (v == this.keys[idx])
|
||||
{
|
||||
return idx;
|
||||
}
|
||||
}
|
||||
}
|
||||
console.log("slider position not found!");
|
||||
console.log(this.values);
|
||||
console.log(v);
|
||||
return 0;
|
||||
}
|
||||
return this.islogarithmic ? null : v;
|
||||
},
|
||||
mapFromRange: function(v){
|
||||
if (this.values)
|
||||
{
|
||||
return this.keys[v];
|
||||
}
|
||||
return this.islogarithmic ? null : v;
|
||||
},
|
||||
fireChange: function(){
|
||||
this.$emit('change',this.value);
|
||||
}
|
||||
},
|
||||
template: `
|
||||
<div class='ln-slider'>
|
||||
<input
|
||||
class="ln-slider"
|
||||
type="range"
|
||||
v-bind:min="min"
|
||||
v-bind:max="max"
|
||||
v-bind:step="step"
|
||||
v-model="sliderValue"
|
||||
@change="fireChange"
|
||||
>{{ displayValue }}
|
||||
</div>`,
|
||||
|
||||
});
|
||||
|
||||
Vue.component("ln-file",{
|
||||
props: {
|
||||
file: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
remove: {
|
||||
type: Function,
|
||||
required: false,
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
},
|
||||
template: `<div
|
||||
class="ln-upload-file"
|
||||
>{{ file.name }} ({{ file.size }}bytes)
|
||||
<sym
|
||||
@click="remove && remove(file)"
|
||||
class="trash"
|
||||
style="position: absolute; right: 2px;"
|
||||
></sym>
|
||||
</div>`
|
||||
});
|
||||
|
||||
Vue.component('ln-upload',{
|
||||
model: {
|
||||
prop: "files",
|
||||
event: "change",
|
||||
},
|
||||
props: {
|
||||
maxSize: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
},
|
||||
files: {
|
||||
type: Array,
|
||||
default: [],
|
||||
}
|
||||
},
|
||||
data: function(){ return { maxSize: this.maxSize, files: this.files, }; },
|
||||
methods: {
|
||||
drop: function(dropEvent){
|
||||
dropEvent.preventDefault();
|
||||
|
||||
for (let n=0;n<dropEvent.dataTransfer.files.length;n++)
|
||||
{
|
||||
//let file = dropEvent.dataTransfer.files[n];
|
||||
let item = dropEvent.dataTransfer.items[n];
|
||||
let file = item.getAsFile();
|
||||
|
||||
this.files.push({
|
||||
name: file.name,
|
||||
size: file.size,
|
||||
type: file.type,
|
||||
file: file,
|
||||
});
|
||||
}
|
||||
|
||||
this.$emit("change",this.files);
|
||||
console.log("files dropped...");
|
||||
},
|
||||
remove(file){
|
||||
console.log(file);
|
||||
let idx = this.files.indexOf(file);
|
||||
if (idx != -1)
|
||||
{
|
||||
this.files.splice(idx,1);
|
||||
this.$emit("change",this.files);
|
||||
}
|
||||
}
|
||||
},
|
||||
template: `
|
||||
<div
|
||||
class="ln-upload"
|
||||
@drop="drop"
|
||||
ondragover="event.preventDefault();"
|
||||
>Dateien hierher ziehen oder auswählen:<br>
|
||||
<input type="file" value=""><br>
|
||||
<ln-file
|
||||
v-for="file in files"
|
||||
v-bind:file="file"
|
||||
v-bind:remove="remove"
|
||||
></ln-file>
|
||||
</div>
|
||||
`,
|
||||
});
|
||||
|
||||
Vue.component('ln-select',{
|
||||
model: {
|
||||
prop: "value",
|
||||
event: "change",
|
||||
},
|
||||
props: {
|
||||
items: {
|
||||
required: true,
|
||||
},
|
||||
value: {
|
||||
required: true,
|
||||
},
|
||||
empty: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
prepared: {
|
||||
get: function(){
|
||||
let prepared = {};
|
||||
LNVue.each(this.items,(element,index) => {
|
||||
prepared[index] = element;
|
||||
});
|
||||
return prepared;
|
||||
},
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
changed: function(ev){
|
||||
this.$emit("change", ev.target.value );
|
||||
},
|
||||
},
|
||||
|
||||
template: `<div class="ln-select">
|
||||
<select @change="changed">
|
||||
<option
|
||||
v-if="empty"
|
||||
value=""
|
||||
></option>
|
||||
<option
|
||||
v-for="(item,key) in prepared"
|
||||
:value="key"
|
||||
:selected="key == value"
|
||||
>{{ item }}</option>
|
||||
</select>
|
||||
</div>
|
||||
`,
|
||||
});
|
||||
|
||||
|
||||
})();
|
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"folders": [
|
||||
{
|
||||
"path": "."
|
||||
}
|
||||
]
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
<div>
|
||||
<h1>webauthn - demo</h1>
|
||||
<div>Use the buttons to start demo action...</div>
|
||||
|
||||
<button
|
||||
@click="LNVue.createCredentials('ABCDEFGHIJKLMNOPQRSTUVWXYZ','LNVue Demo Application','1234567890',true);"
|
||||
>Register</button>
|
||||
|
||||
</div>
|
Loading…
Reference in New Issue