Fixed tabindex and add support for input suggestion to Connector Wizard
This commit is contained in:
@@ -489,6 +489,20 @@ body {
|
|||||||
color: #e9a;
|
color: #e9a;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.form1 > fieldset .field.highlight > input[type="text"],
|
||||||
|
.form1 > fieldset .field.highlight > input[type="file"],
|
||||||
|
.form1 > fieldset .field.highlight > input[type="email"],
|
||||||
|
.form1 > fieldset .field.highlight > input[type="number"],
|
||||||
|
.form1 > fieldset .field.highlight > input[type="search"],
|
||||||
|
.form1 > fieldset .field.highlight > input[type="tel"],
|
||||||
|
.form1 > fieldset .field.highlight > input[type="url"],
|
||||||
|
.form1 > fieldset .field.highlight > input[type="password"],
|
||||||
|
.form1 > fieldset .field.highlight > select,
|
||||||
|
.form1 > fieldset .field.highlight > textarea {
|
||||||
|
background: #666;
|
||||||
|
border-bottom: 2px solid #ccc;
|
||||||
|
}
|
||||||
|
|
||||||
.form1 > fieldset .field.error > input[type="text"],
|
.form1 > fieldset .field.error > input[type="text"],
|
||||||
.form1 > fieldset .field.error > input[type="file"],
|
.form1 > fieldset .field.error > input[type="file"],
|
||||||
.form1 > fieldset .field.error > input[type="email"],
|
.form1 > fieldset .field.error > input[type="email"],
|
||||||
@@ -552,6 +566,7 @@ body {
|
|||||||
border-style: solid;
|
border-style: solid;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.form1 > fieldset .field > button:focus,
|
||||||
.form1 > fieldset .field > button:hover {
|
.form1 > fieldset .field > button:hover {
|
||||||
border-color: #a56;
|
border-color: #a56;
|
||||||
background: #c78;
|
background: #c78;
|
||||||
@@ -562,10 +577,6 @@ body {
|
|||||||
border-color: #a56;
|
border-color: #a56;
|
||||||
}
|
}
|
||||||
|
|
||||||
.form1 > fieldset .field > button:focus {
|
|
||||||
outline: 1px dotted #eee;
|
|
||||||
}
|
|
||||||
|
|
||||||
.form1 > fieldset .field > button.secondary {
|
.form1 > fieldset .field > button.secondary {
|
||||||
float: right;
|
float: right;
|
||||||
background: transparent;
|
background: transparent;
|
||||||
@@ -573,6 +584,7 @@ body {
|
|||||||
border-color: #eee;
|
border-color: #eee;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.form1 > fieldset .field > button.secondary:focus,
|
||||||
.form1 > fieldset .field > button.secondary:hover {
|
.form1 > fieldset .field > button.secondary:hover {
|
||||||
border-color: #ddd;
|
border-color: #ddd;
|
||||||
color: #ddd;
|
color: #ddd;
|
||||||
@@ -582,3 +594,79 @@ body {
|
|||||||
border-color: #666;
|
border-color: #666;
|
||||||
color: #666;
|
color: #666;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.form1 > fieldset .field > ul.input-suggestions {
|
||||||
|
background: #262626;
|
||||||
|
box-shadow: 0 0 3px #0006;
|
||||||
|
border: 1px solid #666;
|
||||||
|
position: relative;
|
||||||
|
margin: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form1 > fieldset .field > ul.input-suggestions::before,
|
||||||
|
.form1 > fieldset .field > ul.input-suggestions::after {
|
||||||
|
top: -5px;
|
||||||
|
left: 5px;
|
||||||
|
position: absolute;
|
||||||
|
z-index: 1;
|
||||||
|
display: block;
|
||||||
|
content: " ";
|
||||||
|
width: 10px;
|
||||||
|
height: 10px;
|
||||||
|
background: #262626;
|
||||||
|
transform: rotate(45deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.form1 > fieldset .field > ul.input-suggestions::after {
|
||||||
|
top: -6px;
|
||||||
|
z-index: 0;
|
||||||
|
background: #666;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form1 > fieldset .field > ul.input-suggestions > li {
|
||||||
|
position: relative;
|
||||||
|
z-index: 2;
|
||||||
|
padding: 10px;
|
||||||
|
cursor: pointer;
|
||||||
|
border-bottom: 1px solid #222;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form1 > fieldset .field > ul.input-suggestions > li:hover,
|
||||||
|
.form1 > fieldset .field > ul.input-suggestions > li.current {
|
||||||
|
background: #555;
|
||||||
|
border-bottom: 1px solid #555;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form1 > fieldset .field > ul.input-suggestions > li:first-child:hover::before,
|
||||||
|
.form1
|
||||||
|
> fieldset
|
||||||
|
.field
|
||||||
|
> ul.input-suggestions
|
||||||
|
> li.current:first-child::before {
|
||||||
|
top: -6px;
|
||||||
|
position: absolute;
|
||||||
|
z-index: 0;
|
||||||
|
left: 5px;
|
||||||
|
display: block;
|
||||||
|
content: " ";
|
||||||
|
width: 10px;
|
||||||
|
height: 10px;
|
||||||
|
background: #555;
|
||||||
|
transform: rotate(45deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.form1 > fieldset .field > ul.input-suggestions > li:last-child {
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form1 > fieldset .field > ul.input-suggestions > li > .sugt-title {
|
||||||
|
color: #fff;
|
||||||
|
font-weight: bold;
|
||||||
|
margin: 0 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form1 > fieldset .field > ul.input-suggestions > li > .sugt-value {
|
||||||
|
color: #fdd;
|
||||||
|
font-size: 0.9em;
|
||||||
|
margin: 0 5px;
|
||||||
|
}
|
||||||
|
|||||||
@@ -61,7 +61,7 @@
|
|||||||
v-for="(field, key) in current.fields"
|
v-for="(field, key) in current.fields"
|
||||||
:key="key"
|
:key="key"
|
||||||
class="field"
|
class="field"
|
||||||
:class="{ error: field.error.length > 0 }"
|
:class="{ error: field.error.length > 0, highlight: field.highlighted }"
|
||||||
>
|
>
|
||||||
{{ field.field.name }}
|
{{ field.field.name }}
|
||||||
|
|
||||||
@@ -74,8 +74,13 @@
|
|||||||
:name="field.field.name"
|
:name="field.field.name"
|
||||||
:placeholder="field.field.example"
|
:placeholder="field.field.example"
|
||||||
:autofocus="field.autofocus"
|
:autofocus="field.autofocus"
|
||||||
@input="verify(key, field, false)"
|
:tabindex="field.tabIndex"
|
||||||
@change="verify(key, field, true)"
|
:disabled="field.field.readonly"
|
||||||
|
@keydown="keydown($event, key, field)"
|
||||||
|
@focus="focus(key, field, true)"
|
||||||
|
@blur="focus(key, field, false)"
|
||||||
|
@input="changed(key, field, false)"
|
||||||
|
@change="changed(key, field, true)"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<input
|
<input
|
||||||
@@ -87,18 +92,28 @@
|
|||||||
:name="field.field.name"
|
:name="field.field.name"
|
||||||
:placeholder="field.field.example"
|
:placeholder="field.field.example"
|
||||||
:autofocus="field.autofocus"
|
:autofocus="field.autofocus"
|
||||||
@input="verify(key, field, false)"
|
:tabindex="field.tabIndex"
|
||||||
@change="verify(key, field, true)"
|
:disabled="field.field.readonly"
|
||||||
|
@focus="focus(key, field, true)"
|
||||||
|
@blur="focus(key, field, false)"
|
||||||
|
@input="changed(key, field, false)"
|
||||||
|
@change="changed(key, field, true)"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<input
|
<input
|
||||||
v-if="field.field.type === 'checkbox'"
|
v-if="field.field.type === 'checkbox'"
|
||||||
v-model="field.field.value"
|
v-model="field.field.value"
|
||||||
|
v-focus="field.autofocus"
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
autocomplete="off"
|
autocomplete="off"
|
||||||
:name="field.field.name"
|
:name="field.field.name"
|
||||||
@input="verify(key, field, false)"
|
:autofocus="field.autofocus"
|
||||||
@change="verify(key, field, true)"
|
:tabindex="field.tabIndex"
|
||||||
|
:disabled="field.field.readonly"
|
||||||
|
@focus="focus(key, field, true)"
|
||||||
|
@blur="focus(key, field, false)"
|
||||||
|
@input="changed(key, field, false)"
|
||||||
|
@change="changed(key, field, true)"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<textarea
|
<textarea
|
||||||
@@ -109,42 +124,56 @@
|
|||||||
:placeholder="field.field.example"
|
:placeholder="field.field.example"
|
||||||
:name="field.field.name"
|
:name="field.field.name"
|
||||||
:autofocus="field.autofocus"
|
:autofocus="field.autofocus"
|
||||||
@input="verify(key, field, false)"
|
:tabindex="field.tabIndex"
|
||||||
|
:disabled="field.field.readonly"
|
||||||
|
@keydown="keydown(key, field)"
|
||||||
|
@focus="focus(key, field, true)"
|
||||||
|
@blur="focus(key, field, false)"
|
||||||
|
@input="changed(key, field, false)"
|
||||||
@keyup="expandTextarea"
|
@keyup="expandTextarea"
|
||||||
@change="verify(key, field, true)"
|
@change="changed(key, field, true)"
|
||||||
></textarea>
|
></textarea>
|
||||||
|
|
||||||
<input
|
<input
|
||||||
v-if="field.field.type === 'textfile'"
|
v-if="field.field.type === 'textfile'"
|
||||||
|
v-focus="field.autofocus"
|
||||||
type="file"
|
type="file"
|
||||||
autocomplete="off"
|
autocomplete="off"
|
||||||
:placeholder="field.field.example"
|
:placeholder="field.field.example"
|
||||||
:name="field.field.name + '-file'"
|
:name="field.field.name + '-file'"
|
||||||
:autofocus="field.autofocus"
|
:autofocus="field.autofocus"
|
||||||
|
:tabindex="field.tabIndex"
|
||||||
|
:disabled="field.field.readonly"
|
||||||
|
@focus="focus(key, field, true)"
|
||||||
|
@blur="focus(key, field, false)"
|
||||||
@change="importFile($event.target, field)"
|
@change="importFile($event.target, field)"
|
||||||
/>
|
/>
|
||||||
<input
|
<input
|
||||||
v-if="field.field.type === 'textfile'"
|
v-if="field.field.type === 'textfile'"
|
||||||
v-model="field.field.value"
|
v-model="field.field.value"
|
||||||
v-focus="field.autofocus"
|
|
||||||
type="text"
|
type="text"
|
||||||
autocomplete="off"
|
autocomplete="off"
|
||||||
:name="field.field.name"
|
:name="field.field.name"
|
||||||
:placeholder="field.field.example"
|
:placeholder="field.field.example"
|
||||||
:autofocus="field.autofocus"
|
|
||||||
style="display: none"
|
style="display: none"
|
||||||
@input="verify(key, field, false)"
|
@input="changed(key, field, false)"
|
||||||
@change="verify(key, field, true)"
|
@change="changed(key, field, true)"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<select
|
<select
|
||||||
v-if="field.field.type === 'select'"
|
v-if="field.field.type === 'select'"
|
||||||
v-model="field.field.value"
|
v-model="field.field.value"
|
||||||
|
v-focus="field.autofocus"
|
||||||
autocomplete="off"
|
autocomplete="off"
|
||||||
:name="field.field.name"
|
:name="field.field.name"
|
||||||
|
:autofocus="field.autofocus"
|
||||||
:value="field.field.value"
|
:value="field.field.value"
|
||||||
@input="verify(key, field, false)"
|
:tabindex="field.tabIndex"
|
||||||
@change="verify(key, field, true)"
|
:disabled="field.field.readonly"
|
||||||
|
@focus="focus(key, field, true)"
|
||||||
|
@blur="focus(key, field, false)"
|
||||||
|
@input="changed(key, field, false)"
|
||||||
|
@change="changed(key, field, true)"
|
||||||
>
|
>
|
||||||
<option
|
<option
|
||||||
v-for="(option, oKey) in field.field.example.split(',')"
|
v-for="(option, oKey) in field.field.example.split(',')"
|
||||||
@@ -169,19 +198,46 @@
|
|||||||
>
|
>
|
||||||
<input
|
<input
|
||||||
v-model="field.field.value"
|
v-model="field.field.value"
|
||||||
|
v-focus="field.autofocus && oKey === 0"
|
||||||
type="radio"
|
type="radio"
|
||||||
autocomplete="off"
|
autocomplete="off"
|
||||||
:name="field.field.name"
|
:name="field.field.name"
|
||||||
|
:autofocus="field.autofocus && oKey === 0"
|
||||||
:value="option"
|
:value="option"
|
||||||
:checked="field.field.value === option"
|
:checked="field.field.value === option"
|
||||||
:aria-checked="field.field.value === option"
|
:aria-checked="field.field.value === option"
|
||||||
@input="verify(key, field, false)"
|
:tabindex="field.nextSubTabIndex(oKey)"
|
||||||
@change="verify(key, field, true)"
|
:disabled="field.field.readonly"
|
||||||
|
@focus="focus(key, field, true)"
|
||||||
|
@blur="focus(key, field, false)"
|
||||||
|
@input="changed(key, field, false)"
|
||||||
|
@change="changed(key, field, true)"
|
||||||
/>
|
/>
|
||||||
{{ option }}
|
{{ option }}
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<ul
|
||||||
|
v-if="field.suggestion.suggestions.length > 0"
|
||||||
|
class="input-suggestions lst-nostyle"
|
||||||
|
@mouseenter="field.holdSuggestions(true)"
|
||||||
|
@mouseleave="field.holdSuggestions(false)"
|
||||||
|
>
|
||||||
|
<li
|
||||||
|
v-for="(suggestion, sKey) in field.suggestion.suggestions"
|
||||||
|
:key="sKey"
|
||||||
|
:class="{ current: sKey === field.suggestion.selected }"
|
||||||
|
@click="clickInputSuggestion(key, field, sKey)"
|
||||||
|
>
|
||||||
|
<div class="sugt-title">
|
||||||
|
{{ suggestion.title }}
|
||||||
|
</div>
|
||||||
|
<div class="sugt-value">
|
||||||
|
{{ suggestion.value }}
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
<div v-if="field.error.length > 0" class="error">{{ field.error }}</div>
|
<div v-if="field.error.length > 0" class="error">{{ field.error }}</div>
|
||||||
<div v-else-if="field.message.length > 0" class="message">
|
<div v-else-if="field.message.length > 0" class="message">
|
||||||
{{ field.message }}
|
{{ field.message }}
|
||||||
@@ -196,8 +252,11 @@
|
|||||||
<div class="field">
|
<div class="field">
|
||||||
<button
|
<button
|
||||||
v-if="current.submittable"
|
v-if="current.submittable"
|
||||||
|
v-focus="submitterTabIndex === 1"
|
||||||
type="submit"
|
type="submit"
|
||||||
:disabled="current.submitting || disabled"
|
:disabled="current.submitting || disabled"
|
||||||
|
:tabindex="submitterTabIndex"
|
||||||
|
:autofocus="submitterTabIndex === 1"
|
||||||
@click="submitAndGetNext"
|
@click="submitAndGetNext"
|
||||||
>
|
>
|
||||||
{{ current.actionText }}
|
{{ current.actionText }}
|
||||||
@@ -205,6 +264,7 @@
|
|||||||
<button
|
<button
|
||||||
v-if="current.cancellable"
|
v-if="current.cancellable"
|
||||||
:disabled="current.submitting || disabled"
|
:disabled="current.submitting || disabled"
|
||||||
|
:tabindex="submitterTabIndex + 1"
|
||||||
class="secondary"
|
class="secondary"
|
||||||
@click="cancelAndGetNext"
|
@click="cancelAndGetNext"
|
||||||
>
|
>
|
||||||
@@ -227,24 +287,16 @@
|
|||||||
<script>
|
<script>
|
||||||
import "./connector.css";
|
import "./connector.css";
|
||||||
import * as command from "../commands/commands.js";
|
import * as command from "../commands/commands.js";
|
||||||
|
import * as fieldBuilder from "./connector_field_builder.js";
|
||||||
|
|
||||||
const preloaderIDPrefix = "connector-resource-preload-control-";
|
const preloaderIDPrefix = "connector-resource-preload-control-";
|
||||||
|
const hightlightClearTimeout = 1000;
|
||||||
function buildField(i, field) {
|
|
||||||
return {
|
|
||||||
verified: false,
|
|
||||||
inputted: false,
|
|
||||||
error: "",
|
|
||||||
message: "",
|
|
||||||
field: field,
|
|
||||||
autofocus: i == 0
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function buildEmptyCurrent() {
|
function buildEmptyCurrent() {
|
||||||
return {
|
return {
|
||||||
data: null,
|
data: null,
|
||||||
alert: false,
|
alert: false,
|
||||||
|
clearHightlightTimeout: null,
|
||||||
title: "",
|
title: "",
|
||||||
message: "",
|
message: "",
|
||||||
fields: [],
|
fields: [],
|
||||||
@@ -279,6 +331,8 @@ export default {
|
|||||||
currentConnectorCloseWait: null,
|
currentConnectorCloseWait: null,
|
||||||
current: buildEmptyCurrent(),
|
current: buildEmptyCurrent(),
|
||||||
preloaderIDName: "",
|
preloaderIDName: "",
|
||||||
|
fieldValueBackup: [],
|
||||||
|
submitterTabIndex: 1,
|
||||||
working: false,
|
working: false,
|
||||||
disabled: false,
|
disabled: false,
|
||||||
cancelled: false
|
cancelled: false
|
||||||
@@ -335,14 +389,21 @@ export default {
|
|||||||
this.current.type = next.type();
|
this.current.type = next.type();
|
||||||
this.current.data = next.data();
|
this.current.data = next.data();
|
||||||
|
|
||||||
|
let fields = null,
|
||||||
|
tabIndex = 1;
|
||||||
|
|
||||||
switch (this.current.type) {
|
switch (this.current.type) {
|
||||||
case command.NEXT_PROMPT:
|
case command.NEXT_PROMPT:
|
||||||
let fields = this.current.data.inputs();
|
fields = this.current.data.inputs();
|
||||||
|
|
||||||
for (let i in fields) {
|
for (let i = 0; i < fields.length; i++) {
|
||||||
this.current.fields.push(buildField(i, fields[i]));
|
const f = fieldBuilder.build(tabIndex, i, fields[i]);
|
||||||
|
|
||||||
|
this.current.fields.push(f);
|
||||||
|
tabIndex = f.nextTabIndex();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.submitterTabIndex = tabIndex > 0 ? tabIndex : 1;
|
||||||
this.current.actionText = this.current.data.actionText();
|
this.current.actionText = this.current.data.actionText();
|
||||||
this.current.submittable = true;
|
this.current.submittable = true;
|
||||||
this.current.alert = true;
|
this.current.alert = true;
|
||||||
@@ -404,7 +465,11 @@ export default {
|
|||||||
|
|
||||||
this.getConnector().wizard.close();
|
this.getConnector().wizard.close();
|
||||||
|
|
||||||
|
try {
|
||||||
await waiter;
|
await waiter;
|
||||||
|
} catch (e) {
|
||||||
|
// Do nothing
|
||||||
|
}
|
||||||
},
|
},
|
||||||
runWizard() {
|
runWizard() {
|
||||||
if (this.currentConnectorCloseWait !== null) {
|
if (this.currentConnectorCloseWait !== null) {
|
||||||
@@ -438,7 +503,7 @@ export default {
|
|||||||
getFieldValues() {
|
getFieldValues() {
|
||||||
let mod = {};
|
let mod = {};
|
||||||
|
|
||||||
for (let i in this.current.fields) {
|
for (let i = 0; i < this.current.fields.length; i++) {
|
||||||
mod[this.current.fields[i].field.name] = this.current.fields[
|
mod[this.current.fields[i].field.name] = this.current.fields[
|
||||||
i
|
i
|
||||||
].field.value;
|
].field.value;
|
||||||
@@ -446,6 +511,45 @@ export default {
|
|||||||
|
|
||||||
return mod;
|
return mod;
|
||||||
},
|
},
|
||||||
|
createFieldValueBackup() {
|
||||||
|
let backup = [];
|
||||||
|
|
||||||
|
for (let i = 0; i < this.current.fields.length; i++) {
|
||||||
|
backup.push(this.current.fields[i].field.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.fieldValueBackup = backup;
|
||||||
|
},
|
||||||
|
clearFieldValueBackup() {
|
||||||
|
this.fieldValueBackup = [];
|
||||||
|
},
|
||||||
|
clearFieldHighlights() {
|
||||||
|
for (let i = 0; i < this.current.fields.length; i++) {
|
||||||
|
this.current.fields[i].highlighted = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
delayedClearFieldHighlights(timeout) {
|
||||||
|
const self = this;
|
||||||
|
|
||||||
|
if (self.clearHightlightTimeout === null) {
|
||||||
|
clearTimeout(self.clearHightlightTimeout);
|
||||||
|
self.clearHightlightTimeout = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.clearHightlightTimeout = setTimeout(() => {
|
||||||
|
self.clearHightlightTimeout = null;
|
||||||
|
self.clearFieldHighlights();
|
||||||
|
}, timeout);
|
||||||
|
},
|
||||||
|
restoreFieldValuesFromBackup(except) {
|
||||||
|
for (let i = 0; i < this.fieldValueBackup.length; i++) {
|
||||||
|
if (except === i) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.current.fields[i].field.value = this.fieldValueBackup[i];
|
||||||
|
}
|
||||||
|
},
|
||||||
expandTextarea(event) {
|
expandTextarea(event) {
|
||||||
event.target.style.overflowY = "hidden";
|
event.target.style.overflowY = "hidden";
|
||||||
event.target.style.height = "";
|
event.target.style.height = "";
|
||||||
@@ -484,7 +588,7 @@ export default {
|
|||||||
verify(key, field, force) {
|
verify(key, field, force) {
|
||||||
try {
|
try {
|
||||||
field.message = "" + field.field.verify(field.field.value);
|
field.message = "" + field.field.verify(field.field.value);
|
||||||
field.inputted = true;
|
field.modified = true;
|
||||||
field.verified = true;
|
field.verified = true;
|
||||||
field.error = "";
|
field.error = "";
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@@ -492,27 +596,27 @@ export default {
|
|||||||
field.message = "";
|
field.message = "";
|
||||||
field.verified = false;
|
field.verified = false;
|
||||||
|
|
||||||
if (field.inputted || force) {
|
if (field.modified || force) {
|
||||||
field.error = "" + e;
|
field.error = "" + e;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
field.highlighted = false;
|
||||||
|
|
||||||
if (
|
if (
|
||||||
!field.verified &&
|
!field.verified &&
|
||||||
(field.inputted || force) &&
|
(field.modified || force) &&
|
||||||
field.error.length <= 0
|
field.error.length <= 0
|
||||||
) {
|
) {
|
||||||
field.error = "Invalid";
|
field.error = "Invalid";
|
||||||
}
|
}
|
||||||
|
|
||||||
this.current.fields[key] = field;
|
|
||||||
|
|
||||||
return field.verified;
|
return field.verified;
|
||||||
},
|
},
|
||||||
verifyAll() {
|
verifyAll() {
|
||||||
let verified = true;
|
let verified = true;
|
||||||
|
|
||||||
for (let i in this.current.fields) {
|
for (let i = 0; i < this.current.fields.length; i++) {
|
||||||
if (this.verify(i, this.current.fields[i], true)) {
|
if (this.verify(i, this.current.fields[i], true)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -522,6 +626,157 @@ export default {
|
|||||||
|
|
||||||
return verified;
|
return verified;
|
||||||
},
|
},
|
||||||
|
focus(key, field, focused) {
|
||||||
|
field.highlighted = false;
|
||||||
|
|
||||||
|
if (!focused) {
|
||||||
|
// Don't reset a holding field
|
||||||
|
if (!field.inputted) {
|
||||||
|
field.resetSuggestions(false);
|
||||||
|
} else if (field.resetSuggestions(false)) {
|
||||||
|
this.clickInputSuggestion(
|
||||||
|
key,
|
||||||
|
field,
|
||||||
|
field.selectedSuggestionIndex()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.createFieldValueBackup();
|
||||||
|
field.reloadSuggestions();
|
||||||
|
},
|
||||||
|
applySuggestion(key, field, suggestion) {
|
||||||
|
this.restoreFieldValuesFromBackup(-1);
|
||||||
|
|
||||||
|
field.field.value = suggestion.value;
|
||||||
|
|
||||||
|
for (let i = 0; i < this.current.fields.length; i++) {
|
||||||
|
this.current.fields[i].highlighted = false;
|
||||||
|
|
||||||
|
if (
|
||||||
|
i === key ||
|
||||||
|
this.current.fields[i].inputted ||
|
||||||
|
this.current.fields[i].field.readonly
|
||||||
|
) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
typeof suggestion.fields[this.current.fields[i].field.name] ===
|
||||||
|
"undefined"
|
||||||
|
) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.current.fields[i].field.value =
|
||||||
|
suggestion.fields[this.current.fields[i].field.name];
|
||||||
|
|
||||||
|
if (!this.verify(i, this.current.fields[i], true)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.current.fields[i].highlighted = true;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
applySuggestionAndVerify(key, field, force, suggestion) {
|
||||||
|
field.inputted = true;
|
||||||
|
|
||||||
|
this.applySuggestion(key, field, suggestion);
|
||||||
|
|
||||||
|
return this.verify(key, field, force);
|
||||||
|
},
|
||||||
|
changed(key, field, force) {
|
||||||
|
this.createFieldValueBackup();
|
||||||
|
|
||||||
|
field.highlighted = false;
|
||||||
|
field.inputted = true;
|
||||||
|
field.enableInputSuggestionsOnAllInput();
|
||||||
|
field.reloadSuggestions();
|
||||||
|
|
||||||
|
this.verify(key, field, force);
|
||||||
|
},
|
||||||
|
keydown(event, key, field) {
|
||||||
|
switch (event.key) {
|
||||||
|
case "ArrowUp":
|
||||||
|
event.preventDefault();
|
||||||
|
field.moveSuggestionsCursor(true);
|
||||||
|
|
||||||
|
this.applySuggestionAndVerify(
|
||||||
|
key,
|
||||||
|
field,
|
||||||
|
true,
|
||||||
|
field.curentSuggestion()
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "ArrowDown":
|
||||||
|
event.preventDefault();
|
||||||
|
field.moveSuggestionsCursor(false);
|
||||||
|
|
||||||
|
this.applySuggestionAndVerify(
|
||||||
|
key,
|
||||||
|
field,
|
||||||
|
true,
|
||||||
|
field.curentSuggestion()
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "Escape":
|
||||||
|
if (!field.suggestionsPending()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
event.preventDefault();
|
||||||
|
|
||||||
|
this.restoreFieldValuesFromBackup(key);
|
||||||
|
this.clearFieldValueBackup();
|
||||||
|
this.clearFieldHighlights();
|
||||||
|
this.verify(key, field, true);
|
||||||
|
|
||||||
|
field.disableSuggestionsForInput(field.field.value);
|
||||||
|
field.resetSuggestions(true);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "Enter":
|
||||||
|
if (!field.suggestionsPending()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
event.preventDefault();
|
||||||
|
|
||||||
|
this.clickInputSuggestion(
|
||||||
|
key,
|
||||||
|
field,
|
||||||
|
field.selectedSuggestionIndex()
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
clickInputSuggestion(key, field, index) {
|
||||||
|
const self = this;
|
||||||
|
|
||||||
|
field.selectSuggestion(index);
|
||||||
|
|
||||||
|
if (
|
||||||
|
self.applySuggestionAndVerify(
|
||||||
|
key,
|
||||||
|
field,
|
||||||
|
true,
|
||||||
|
field.curentSuggestion()
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
field.disableSuggestionsForInput(field.field.value);
|
||||||
|
} else {
|
||||||
|
field.enableInputSuggestionsOnAllInput();
|
||||||
|
}
|
||||||
|
|
||||||
|
field.resetSuggestions(true);
|
||||||
|
|
||||||
|
self.clearFieldValueBackup();
|
||||||
|
self.delayedClearFieldHighlights(hightlightClearTimeout);
|
||||||
|
},
|
||||||
async submitAndGetNext() {
|
async submitAndGetNext() {
|
||||||
if (this.current.submitting || this.disabled) {
|
if (this.current.submitting || this.disabled) {
|
||||||
return;
|
return;
|
||||||
|
|||||||
213
ui/widgets/connector_field_builder.js
Normal file
213
ui/widgets/connector_field_builder.js
Normal file
@@ -0,0 +1,213 @@
|
|||||||
|
// Sshwifty - A Web SSH client
|
||||||
|
//
|
||||||
|
// Copyright (C) 2019-2020 Rui NI <nirui@gmx.com>
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Affero General Public License as
|
||||||
|
// published by the Free Software Foundation, either version 3 of the
|
||||||
|
// License, or (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Affero General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Affero General Public License
|
||||||
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
function getTabIndex(tabIndex, field) {
|
||||||
|
if (field.readonly) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (field.type) {
|
||||||
|
case "text":
|
||||||
|
case "password":
|
||||||
|
case "checkbox":
|
||||||
|
case "textarea":
|
||||||
|
case "textfile":
|
||||||
|
case "select":
|
||||||
|
case "radio":
|
||||||
|
return tabIndex;
|
||||||
|
|
||||||
|
default:
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function build(tabIndex, i, field) {
|
||||||
|
return {
|
||||||
|
verified: false,
|
||||||
|
modified: false,
|
||||||
|
inputted: false,
|
||||||
|
highlighted: false,
|
||||||
|
error: "",
|
||||||
|
message: "",
|
||||||
|
field: field,
|
||||||
|
autofocus: tabIndex === 1 && !field.readonly,
|
||||||
|
tabIndex: getTabIndex(tabIndex, field),
|
||||||
|
blockedSuggestionValue: "",
|
||||||
|
blockingSuggestion: false,
|
||||||
|
nextTabIndex() {
|
||||||
|
if (this.field.readonly) {
|
||||||
|
return this.tabIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (this.field.type) {
|
||||||
|
case "radio":
|
||||||
|
return this.tabIndex + this.field.example.split(",").length;
|
||||||
|
|
||||||
|
default:
|
||||||
|
return this.tabIndex + 1;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
nextSubTabIndex(subIndex) {
|
||||||
|
if (this.field.readonly) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.tabIndex + subIndex;
|
||||||
|
},
|
||||||
|
suggestion: {
|
||||||
|
selected: -1,
|
||||||
|
suggestions: [],
|
||||||
|
orignalValue: "",
|
||||||
|
orignalValueStored: false,
|
||||||
|
holding: false,
|
||||||
|
needsReset: false,
|
||||||
|
reset() {
|
||||||
|
this.selected = -1;
|
||||||
|
this.suggestions = [];
|
||||||
|
this.holding = false;
|
||||||
|
this.needsReset = false;
|
||||||
|
this.clearStored();
|
||||||
|
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
softReset() {
|
||||||
|
if (this.holding) {
|
||||||
|
this.needsReset = true;
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.reset();
|
||||||
|
},
|
||||||
|
hold(toHold) {
|
||||||
|
this.holding = toHold;
|
||||||
|
|
||||||
|
if (this.holding || !this.needsReset) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.reset();
|
||||||
|
},
|
||||||
|
storeOrignal(val) {
|
||||||
|
if (this.orignalValueStored) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.orignalValue = val;
|
||||||
|
this.orignalValueStored = true;
|
||||||
|
},
|
||||||
|
loadStored(defaultValue) {
|
||||||
|
return this.orignalValueStored ? this.orignalValue : defaultValue;
|
||||||
|
},
|
||||||
|
clearStored() {
|
||||||
|
this.orignalValue = "";
|
||||||
|
this.orignalValueStored = false;
|
||||||
|
},
|
||||||
|
select(index, fieldValue) {
|
||||||
|
if (this.selected < 0) {
|
||||||
|
this.storeOrignal(fieldValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (index < -1 || index >= this.suggestions.length) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.selected = index;
|
||||||
|
},
|
||||||
|
cursorUp(fieldValue) {
|
||||||
|
this.select(this.selected - 1, fieldValue);
|
||||||
|
},
|
||||||
|
cursorDown(fieldValue) {
|
||||||
|
this.select(this.selected + 1, fieldValue);
|
||||||
|
},
|
||||||
|
cursorMove(toUp, fieldValue) {
|
||||||
|
toUp ? this.cursorUp(fieldValue) : this.cursorDown(fieldValue);
|
||||||
|
},
|
||||||
|
reload(fieldValue, suggestions) {
|
||||||
|
this.selected = -1;
|
||||||
|
this.suggestions = [];
|
||||||
|
|
||||||
|
this.clearStored();
|
||||||
|
|
||||||
|
if (suggestions.length === 1 && suggestions[0].value === fieldValue) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let v in suggestions) {
|
||||||
|
this.suggestions.push({
|
||||||
|
title: suggestions[v].title,
|
||||||
|
value: suggestions[v].value,
|
||||||
|
fields: suggestions[v].meta
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
current(defaultValue) {
|
||||||
|
if (this.selected < 0) {
|
||||||
|
return {
|
||||||
|
title: "Input",
|
||||||
|
value: this.loadStored(defaultValue),
|
||||||
|
fields: {}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.suggestions[this.selected];
|
||||||
|
}
|
||||||
|
},
|
||||||
|
disableSuggestionsForInput(val) {
|
||||||
|
this.blockedSuggestionValue = val;
|
||||||
|
this.blockingSuggestion = true;
|
||||||
|
},
|
||||||
|
enableInputSuggestionsOnAllInput() {
|
||||||
|
this.blockedSuggestionValue = "";
|
||||||
|
this.blockingSuggestion = false;
|
||||||
|
},
|
||||||
|
suggestionsPending() {
|
||||||
|
return this.suggestion.suggestions.length > 0;
|
||||||
|
},
|
||||||
|
reloadSuggestions() {
|
||||||
|
if (
|
||||||
|
this.blockingSuggestion &&
|
||||||
|
this.field.value === this.blockedSuggestionValue
|
||||||
|
) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.suggestion.reload(
|
||||||
|
this.field.value,
|
||||||
|
this.field.suggestions(this.field.value)
|
||||||
|
);
|
||||||
|
},
|
||||||
|
resetSuggestions(force) {
|
||||||
|
return force ? this.suggestion.reset() : this.suggestion.softReset();
|
||||||
|
},
|
||||||
|
holdSuggestions(toHold) {
|
||||||
|
this.suggestion.hold(toHold);
|
||||||
|
},
|
||||||
|
moveSuggestionsCursor(toUp) {
|
||||||
|
this.suggestion.cursorMove(toUp, this.field.value);
|
||||||
|
},
|
||||||
|
selectSuggestion(index) {
|
||||||
|
this.suggestion.select(index, this.field.value);
|
||||||
|
},
|
||||||
|
curentSuggestion() {
|
||||||
|
return this.suggestion.current(this.field.value);
|
||||||
|
},
|
||||||
|
selectedSuggestionIndex() {
|
||||||
|
return this.suggestion.selected;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user