mirror of
https://github.com/the-djmaze/snappymail.git
synced 2026-07-11 00:14:50 +03:00
Revamp saveTrigger
This commit is contained in:
parent
325f3c8eea
commit
ce4ee3ba3f
15 changed files with 72 additions and 179 deletions
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
38
dev/External/Admin/ko.js
vendored
38
dev/External/Admin/ko.js
vendored
|
|
@ -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
44
dev/External/ko.js
vendored
|
|
@ -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) =>
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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">
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
||||
<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>
|
||||
|
||||
<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"/>
|
||||
|
||||
<span data-i18n="MB"></span>
|
||||
|
||||
|
||||
<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">
|
||||
|
|
|
|||
|
|
@ -3,12 +3,7 @@
|
|||
<!-- ko if: labeled -->
|
||||
|
||||
<span class="i18n" data-bind="attr: {'data-i18n': label}"></span>
|
||||
|
||||
<!-- /ko -->
|
||||
<!-- ko if: triggered -->
|
||||
|
||||
<div data-bind="component: {
|
||||
name: 'SaveTrigger',
|
||||
params: { value: trigger }
|
||||
}"></div>
|
||||
<!-- /ko -->
|
||||
<span data-bind="saveTrigger: trigger"></span>
|
||||
<!-- /ko -->
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
@ -8,13 +8,8 @@
|
|||
<!-- ko if: labeled -->
|
||||
|
||||
<span class="i18n" data-bind="attr: {'data-i18n': label}"></span>
|
||||
|
||||
<!-- /ko -->
|
||||
<!-- ko if: triggered -->
|
||||
|
||||
<div data-bind="component: {
|
||||
name: 'SaveTrigger',
|
||||
params: { value: trigger }
|
||||
}"></div>
|
||||
<span data-bind="saveTrigger: trigger"></span>
|
||||
<!-- /ko -->
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -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 -->
|
||||
|
||||
<div data-bind="component: {
|
||||
name: 'SaveTrigger',
|
||||
params: { value: trigger, verticalAlign: 'top' }
|
||||
}"></div>
|
||||
<!-- /ko -->
|
||||
<span data-bind="saveTrigger: trigger"></span>
|
||||
<!-- /ko -->
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
||||
<div data-bind="component: {
|
||||
name: 'SaveTrigger',
|
||||
params: { value: languageTrigger }
|
||||
}"></div>
|
||||
<span data-bind="saveTrigger: languageTrigger"></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -2,11 +2,7 @@
|
|||
<div class="form-horizontal">
|
||||
<div class="legend">
|
||||
<span class="i18n" data-i18n="SETTINGS_THEMES/LEGEND_THEMES"></span>
|
||||
|
||||
<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">
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue