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 { LanguageStore } from 'Stores/Language';
import { ThemeStore } from 'Stores/Theme'; import { ThemeStore } from 'Stores/Theme';
import { SaveTriggerComponent } from 'Component/SaveTrigger';
import { InputComponent } from 'Component/Input'; import { InputComponent } from 'Component/Input';
import { SelectComponent } from 'Component/Select'; import { SelectComponent } from 'Component/Select';
import { TextAreaComponent } from 'Component/TextArea'; import { TextAreaComponent } from 'Component/TextArea';
@ -107,7 +106,6 @@ export class AbstractApp {
} }
}); });
register('SaveTrigger', SaveTriggerComponent);
register('Input', InputComponent); register('Input', InputComponent);
register('Select', SelectComponent); register('Select', SelectComponent);
register('TextArea', TextAreaComponent); 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 ko from 'ko';
import { SaveSettingsStep } from 'Common/Enums'; 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 // functions
ko.observable.fn.idleTrigger = function() { 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 { 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 const
isFunction = v => typeof v === 'function', 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 // extenders
ko.extenders.limitedList = (target, limitedList) => { ko.extenders.limitedList = (target, limitedList) => {
@ -233,17 +257,7 @@ ko.extenders.toggleSubscribeProperty = (target, options) => {
}; };
ko.extenders.falseTimeout = (target, option) => { ko.extenders.falseTimeout = (target, option) => {
target.iFalseTimeoutTimeout = 0; target.subscribe((() => target(false)).debounce(parseInt(option, 10) || 0));
target.subscribe(value => {
if (value) {
clearTimeout(target.iFalseTimeoutTimeout);
target.iFalseTimeoutTimeout = setTimeout(() => {
target(false);
target.iFalseTimeoutTimeout = 0;
}, parseInt(option, 10) || 0);
}
});
return target; return target;
}; };
@ -254,12 +268,8 @@ ko.observable.fn.deleteAccessHelper = function() {
}; };
ko.addObservablesTo = (target, observables) => { ko.addObservablesTo = (target, observables) => {
Object.entries(observables).forEach(([key, value]) => target[key] = ko.observable(value) );
/*
Object.entries(observables).forEach(([key, 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) => ko.addComputablesTo = (target, computables) =>

View file

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

View file

@ -37,7 +37,7 @@
} }
.e-item.selected > .e-link { .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, .e-item:not(.disabled) > .e-link:focus,
@ -92,48 +92,54 @@
} }
} }
.settings-saved-trigger { .settings-save-trigger {
display: inline-block; display: inline-block;
height: 17px; height: 1em;
line-height: 17px; line-height: 1em;
font-size: 16px;
position: relative;
.animated { &::after {
color: #888; font-family: "snappymail";
content: " ";
display: block;
margin-left: 1em;
opacity: 0;
transition: opacity 1s linear;
} }
&.saving::after {
.success { 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; color: #080;
} content: "✔";
.error {
color: #F00;
}
i {
transition: opacity 2s linear;
position: absolute;
}
i:not([hidden]) {
opacity: 1; opacity: 1;
} }
i[hidden] { &.error::after {
opacity: 0; color: #F00;
content: "✖";
opacity: 1;
} }
} }
textarea + .settings-save-trigger {
vertical-align: top;
}
.settings-saved-trigger-input { .settings-saved-trigger-input {
transition: border-color 0.5s linear; transition: border-color 0.5s linear;
&.success { &.success {
border-color: green !important; border-color: green;
} }
&.error { &.error {
border-color: red !important; border-color: red;
} }
} }

View file

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

View file

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

View file

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

View file

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

View file

@ -2,9 +2,7 @@
<div class="b-toolbar btn-toolbar hide-on-mobile"> <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()}"> <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> <i class="icon-paper-plane"></i>
<span class="btn-text-wrp buttonComposeText"> <span class="buttonComposeText" data-i18n="FOLDER_LIST/BUTTON_NEW_MESSAGE"></span>
<span class="i18n" data-i18n="FOLDER_LIST/BUTTON_NEW_MESSAGE"></span>
</span>
</a> </a>
<a class="btn buttonContacts" data-bind="visible: allowContacts, click: contactsClick, tooltip: 'GLOBAL/CONTACTS'"> <a class="btn buttonContacts" data-bind="visible: allowContacts, click: contactsClick, tooltip: 'GLOBAL/CONTACTS'">
<i class="icon-address-book"></i> <i class="icon-address-book"></i>

View file

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

View file

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