Revamp saveTrigger

This commit is contained in:
djmaze 2021-02-11 10:08:27 +01:00
parent 325f3c8eea
commit ce4ee3ba3f
15 changed files with 72 additions and 179 deletions

View file

@ -14,7 +14,6 @@ import { i18nToNodes, initOnStartOrLangChange } from 'Common/Translator';
import { LanguageStore } from 'Stores/Language';
import { ThemeStore } from 'Stores/Theme';
import { SaveTriggerComponent } from 'Component/SaveTrigger';
import { InputComponent } from 'Component/Input';
import { SelectComponent } from 'Component/Select';
import { TextAreaComponent } from 'Component/TextArea';
@ -107,7 +106,6 @@ export class AbstractApp {
}
});
register('SaveTrigger', SaveTriggerComponent);
register('Input', InputComponent);
register('Select', SelectComponent);
register('TextArea', TextAreaComponent);

View file

@ -1,38 +0,0 @@
import { SaveSettingsStep } from 'Common/Enums';
import { AbstractComponent } from 'Component/Abstract';
export class SaveTriggerComponent extends AbstractComponent {
/**
* @param {Object} params
*/
constructor(params) {
super();
const el = params.element;
this.element = el || null;
this.value = params.value && params.value.subscribe ? params.value : null;
if (el) {
if (this.value) {
el.style.display = 'inline-block';
if (params.verticalAlign) {
el.style.verticalAlign = params.verticalAlign;
}
this.setState(this.value());
this.disposable.push(this.value.subscribe(this.setState, this));
} else {
el.style.display = 'none';
}
}
}
setState(value) {
value = parseInt(value,10);
this.element.querySelector('.icon-spinner').hidden = value !== SaveSettingsStep.Animate;
this.element.querySelector('.success').hidden = value !== SaveSettingsStep.TrueResult;
this.element.querySelector('.error').hidden = value !== SaveSettingsStep.FalseResult;
}
}

View file

@ -2,43 +2,9 @@ import 'External/ko';
import ko from 'ko';
import { SaveSettingsStep } from 'Common/Enums';
ko.bindingHandlers.saveTrigger = {
init: (element) => {
element.saveTriggerType = element.matches('input[type=text],input[type=email],input[type=password],select,textarea')
? 'input' : 'custom';
if ('custom' === element.saveTriggerType) {
element.append(
'  ',
Element.fromHTML('<i class="icon-spinner"></i>'),
Element.fromHTML('<i class="fontastic error">✖</i>'),
Element.fromHTML('<i class="icon-ok success"></i>')
);
element.classList.add('settings-saved-trigger');
} else {
element.classList.add('settings-saved-trigger-input');
}
},
update: (element, fValueAccessor) => {
const value = parseInt(ko.unwrap(fValueAccessor()),10);
if ('custom' === element.saveTriggerType) {
element.querySelector('.icon-spinner').hidden = value !== SaveSettingsStep.Animate;
element.querySelector('.success').hidden = value !== SaveSettingsStep.TrueResult;
element.querySelector('.error').hidden = value !== SaveSettingsStep.FalseResult;
} else if (value !== SaveSettingsStep.Animate) {
element.classList.toggle('success', value === SaveSettingsStep.TrueResult);
element.classList.toggle('error', value === SaveSettingsStep.FalseResult);
}
}
};
ko.extenders.idleTrigger = (target) => {
target.trigger = ko.observable(SaveSettingsStep.Idle);
return target;
};
// functions
ko.observable.fn.idleTrigger = function() {
return this.extend({ 'idleTrigger': true });
this.trigger = ko.observable(SaveSettingsStep.Idle);
return this;
};

44
dev/External/ko.js vendored
View file

@ -1,5 +1,6 @@
import { i18n, i18nToNodes, trigger } from 'Common/Translator';
import { doc, dropdownVisibility } from 'Common/Globals';
import { doc, createElement, dropdownVisibility } from 'Common/Globals';
import { SaveSettingsStep } from 'Common/Enums';
const
isFunction = v => typeof v === 'function',
@ -170,6 +171,29 @@ ko.bindingHandlers.command = {
}
};
ko.bindingHandlers.saveTrigger = {
init: (element) => {
let icon = element;
if (element.matches('input,select,textarea')) {
element.classList.add('settings-saved-trigger-input');
element.after(element.saveTriggerIcon = icon = createElement('span'));
}
icon.classList.add('settings-save-trigger');
},
update: (element, fValueAccessor) => {
const value = parseInt(ko.unwrap(fValueAccessor()),10);
let cl = (element.saveTriggerIcon || element).classList;
if (element.saveTriggerIcon) {
cl.toggle('saving', value === SaveSettingsStep.Animate);
cl.toggle('success', value === SaveSettingsStep.TrueResult);
cl.toggle('error', value === SaveSettingsStep.FalseResult);
}
cl = element.classList;
cl.toggle('success', value === SaveSettingsStep.TrueResult);
cl.toggle('error', value === SaveSettingsStep.FalseResult);
}
};
// extenders
ko.extenders.limitedList = (target, limitedList) => {
@ -233,17 +257,7 @@ ko.extenders.toggleSubscribeProperty = (target, options) => {
};
ko.extenders.falseTimeout = (target, option) => {
target.iFalseTimeoutTimeout = 0;
target.subscribe(value => {
if (value) {
clearTimeout(target.iFalseTimeoutTimeout);
target.iFalseTimeoutTimeout = setTimeout(() => {
target(false);
target.iFalseTimeoutTimeout = 0;
}, parseInt(option, 10) || 0);
}
});
target.subscribe((() => target(false)).debounce(parseInt(option, 10) || 0));
return target;
};
@ -254,12 +268,8 @@ ko.observable.fn.deleteAccessHelper = function() {
};
ko.addObservablesTo = (target, observables) => {
Object.entries(observables).forEach(([key, value]) => target[key] = ko.observable(value) );
/*
Object.entries(observables).forEach(([key, value]) =>
target[key] = Array.isArray(value) ? ko.observableArray(value) : ko.observable(value)
);
*/
target[key] = /*Array.isArray(value) ? ko.observableArray(value) :*/ ko.observable(value) );
};
ko.addComputablesTo = (target, computables) =>

View file

@ -250,17 +250,11 @@
}
.btn {
.btn-text-wrp {
padding-left: 7px;
white-space: normal;
}
&.buttonCompose {
width: calc(~'100% - 85px');
max-width: -moz-fit-content;
max-width: -webkit-fit-content;
max-width: fit-content;
white-space: nowrap;
overflow: hidden;
height: 19px;
margin-top: 1px;

View file

@ -37,7 +37,7 @@
}
.e-item.selected > .e-link {
background-color: var(--dropdown-menu-selected-bg-color, #eee) !important;
background-color: var(--dropdown-menu-selected-bg-color, #eee);
}
.e-item:not(.disabled) > .e-link:focus,
@ -92,48 +92,54 @@
}
}
.settings-saved-trigger {
.settings-save-trigger {
display: inline-block;
height: 17px;
line-height: 17px;
font-size: 16px;
position: relative;
height: 1em;
line-height: 1em;
.animated {
color: #888;
&::after {
font-family: "snappymail";
content: " ";
display: block;
margin-left: 1em;
opacity: 0;
transition: opacity 1s linear;
}
.success {
&.saving::after {
animation: rotation .8s infinite ease-in-out;
border: 3px solid transparent;
border-radius: 100%;
border-top-color: #999;
height: 11px;
opacity: 1;
width: 11px;
}
&.success::after {
color: #080;
}
.error {
color: #F00;
}
i {
transition: opacity 2s linear;
position: absolute;
}
i:not([hidden]) {
content: "✔";
opacity: 1;
}
i[hidden] {
opacity: 0;
&.error::after {
color: #F00;
content: "✖";
opacity: 1;
}
}
textarea + .settings-save-trigger {
vertical-align: top;
}
.settings-saved-trigger-input {
transition: border-color 0.5s linear;
&.success {
border-color: green !important;
border-color: green;
}
&.error {
border-color: red !important;
border-color: red;
}
}

View file

@ -45,7 +45,6 @@
<div class="controls">
<input type="text" class="span6" autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false"
data-bind="value: pdoDsn, saveTrigger: pdoDsnTrigger" />
<div data-bind="saveTrigger: pdoDsnTrigger"></div>
<blockquote style="margin: 10px 0 0 0">
<p class="muted">
mysql:host=127.0.0.1;port=3306;dbname=rainloop<br />
@ -59,7 +58,6 @@ pgsql:host=127.0.0.1;port=5432;dbname=rainloop
<div class="controls">
<input type="text" autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false"
data-bind="value: pdoUser, saveTrigger: pdoUserTrigger" />
<div data-bind="saveTrigger: pdoUserTrigger"></div>
</div>
</div>
<div class="control-group">
@ -67,7 +65,6 @@ pgsql:host=127.0.0.1;port=5432;dbname=rainloop
<div class="controls">
<input type="password" autocomplete="current-password" autocorrect="off" autocapitalize="off" spellcheck="false"
data-bind="value: pdoPassword, saveTrigger: pdoPasswordTrigger" />
<div data-bind="saveTrigger: pdoPasswordTrigger"></div>
</div>
</div>
<div class="control-group">

View file

@ -23,11 +23,7 @@
<div class="controls">
<div class="flag-selector">
<span class="flag-name" tabindex="0" data-bind="text: languageFullName, click: selectLanguage, onSpace: selectLanguage, onEnter: selectLanguage"></span>
&nbsp;&nbsp;
<div data-bind="component: {
name: 'SaveTrigger',
params: { value: languageTrigger }
}"></div>
<span data-bind="saveTrigger: languageTrigger"></span>
</div>
</div>
</div>
@ -39,11 +35,7 @@
<span data-bind="css: 'flag flag-' + languageAdmin().toLowerCase()"></span>
</span>
<span class="flag-name" tabindex="0" data-bind="text: languageAdminFullName, click: selectLanguageAdmin, onSpace: selectLanguageAdmin, onEnter: selectLanguageAdmin"></span>
&nbsp;&nbsp;
<div data-bind="component: {
name: 'SaveTrigger',
params: { value: languageAdminTrigger }
}"></div>
<span data-bind="saveTrigger: languageAdminTrigger"></span>
</div>
</div>
</div>
@ -114,12 +106,7 @@
<input type="number" min="1" step="1" class="span1" data-bind="textInput: mainAttachmentLimit"/>
&nbsp;
<span data-i18n="MB"></span>
&nbsp;
&nbsp;
<div data-bind="component: {
name: 'SaveTrigger',
params: { value: attachmentLimitTrigger }
}"></div>
<span data-bind="saveTrigger: attachmentLimitTrigger"></span>
<br />
<br />
<span class="well well-small" data-bind="visible: '' !== uploadDataDesc">

View file

@ -3,12 +3,7 @@
<!-- ko if: labeled -->
&nbsp;
<span class="i18n" data-bind="attr: {'data-i18n': label}"></span>
&nbsp;
<!-- /ko -->
<!-- ko if: triggered -->
&nbsp;
<div data-bind="component: {
name: 'SaveTrigger',
params: { value: trigger }
}"></div>
<!-- /ko -->
<span data-bind="saveTrigger: trigger"></span>
<!-- /ko -->

View file

@ -1,3 +0,0 @@
<div class="e-component settings-saved-trigger">
<i class="icon-spinner"></i><i class="fontastic error"></i><i class="icon-ok success"></i>
</div>

View file

@ -8,13 +8,8 @@
<!-- ko if: labeled -->
&nbsp;
<span class="i18n" data-bind="attr: {'data-i18n': label}"></span>
&nbsp;
<!-- /ko -->
<!-- ko if: triggered -->
&nbsp;
<div data-bind="component: {
name: 'SaveTrigger',
params: { value: trigger }
}"></div>
<span data-bind="saveTrigger: trigger"></span>
<!-- /ko -->
</div>

View file

@ -1,9 +1,5 @@
<textarea class="i18n" rows="5" autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false"
data-bind="value: value, enable: enable, attr: { 'placeholder': placeholder, 'rows': rows, 'spellcheck': spellcheck ? 'true' : 'false' }, css: className"></textarea>
<!-- ko if: triggered -->
&nbsp;
<div data-bind="component: {
name: 'SaveTrigger',
params: { value: trigger, verticalAlign: 'top' }
}"></div>
<!-- /ko -->
<span data-bind="saveTrigger: trigger"></span>
<!-- /ko -->

View file

@ -2,9 +2,7 @@
<div class="b-toolbar btn-toolbar hide-on-mobile">
<a class="btn buttonCompose" data-bind="visible: allowComposer, click: composeClick, tooltip: 'FOLDER_LIST/BUTTON_NEW_MESSAGE', css: {'btn-warning': composeInEdit, 'btn-success': !composeInEdit()}">
<i class="icon-paper-plane"></i>
<span class="btn-text-wrp buttonComposeText">
<span class="i18n" data-i18n="FOLDER_LIST/BUTTON_NEW_MESSAGE"></span>
</span>
<span class="buttonComposeText" data-i18n="FOLDER_LIST/BUTTON_NEW_MESSAGE"></span>
</a>
<a class="btn buttonContacts" data-bind="visible: allowContacts, click: contactsClick, tooltip: 'GLOBAL/CONTACTS'">
<i class="icon-address-book"></i>

View file

@ -21,11 +21,7 @@
<div class="controls">
<div class="flag-selector">
<span class="flag-name" tabindex="0" data-bind="text: languageFullName, click: selectLanguage, onSpace: selectLanguage, onEnter: selectLanguage"></span>
&nbsp;&nbsp;
<div data-bind="component: {
name: 'SaveTrigger',
params: { value: languageTrigger }
}"></div>
<span data-bind="saveTrigger: languageTrigger"></span>
</div>
</div>
</div>

View file

@ -2,11 +2,7 @@
<div class="form-horizontal">
<div class="legend">
<span class="i18n" data-i18n="SETTINGS_THEMES/LEGEND_THEMES"></span>
&nbsp;&nbsp;
<div data-bind="component: {
name: 'SaveTrigger',
params: { value: themeTrigger }
}"></div>
<span data-bind="saveTrigger: themeTrigger"></span>
</div>
</div>
<div class="b-themes-list" data-bind="foreach: themesObjects">