diff --git a/dev/App/User.js b/dev/App/User.js index b18f8a5d9..4446c6054 100644 --- a/dev/App/User.js +++ b/dev/App/User.js @@ -48,6 +48,7 @@ import { IdentityModel } from 'Model/Identity'; import { LoginUserScreen } from 'Screen/User/Login'; import { MailBoxUserScreen } from 'Screen/User/MailBox'; +import { ContactsUserScreen } from 'Screen/User/Contacts'; import { SettingsUserScreen } from 'Screen/User/Settings'; import { startScreens, showScreenPopup, arePopupsVisible } from 'Knoin/Knoin'; @@ -211,6 +212,7 @@ export class AppUser extends AbstractApp { if (value) { startScreens([ MailBoxUserScreen, + ContactsUserScreen, SettingsUserScreen ]); diff --git a/dev/Common/Enums.js b/dev/Common/Enums.js index 028d2a700..42c896e68 100644 --- a/dev/Common/Enums.js +++ b/dev/Common/Enums.js @@ -9,6 +9,7 @@ ScopeMessageList = 'MessageList', ScopeFolderList = 'FolderList', ScopeMessageView = 'MessageView', ScopeSettings = 'Settings', +ScopeContacts = 'Contacts', /** * @enum {number} diff --git a/dev/Screen/User/Contacts.js b/dev/Screen/User/Contacts.js new file mode 100644 index 000000000..cf4fef2f9 --- /dev/null +++ b/dev/Screen/User/Contacts.js @@ -0,0 +1,33 @@ +import { ScopeContacts } from 'Common/Enums'; +import { i18n } from 'Common/Translator'; + +import { AppUserStore } from 'Stores/User/App'; + +import { SystemDropDownUserView } from 'View/User/SystemDropDown'; +import { AddressBooks } from 'View/User/Contacts/AddressBooks'; +import { ContactsList } from 'View/User/Contacts/ContactsList'; +import { ContactView } from 'View/User/Contacts/ContactView'; + +import { AbstractScreen } from 'Knoin/AbstractScreen'; + +export class ContactsUserScreen extends AbstractScreen { + + constructor() { + super('contacts', [ + SystemDropDownUserView, + AddressBooks, + ContactsList, + ContactView + ]); + } + + onShow() { + this.setTitle(); + AppUserStore.focusedState('none'); + AppUserStore.focusedState(ScopeContacts); + } + + setTitle() { + rl.setTitle(i18n('GLOBAL/CONTACTS')); + } +} diff --git a/dev/Screen/User/MailBox.js b/dev/Screen/User/MailBox.js index 8450f7648..16028dd0e 100644 --- a/dev/Screen/User/MailBox.js +++ b/dev/Screen/User/MailBox.js @@ -39,7 +39,7 @@ export class MailBoxUserScreen extends AbstractScreen { /** * @returns {void} */ - updateWindowTitle() { + setTitle() { const count = Settings.app('listPermanentFiltered') ? 0 : FolderUserStore.foldersInboxUnreadCount(), email = AccountUserStore.email(); @@ -55,7 +55,7 @@ export class MailBoxUserScreen extends AbstractScreen { * @returns {void} */ onShow() { - this.updateWindowTitle(); + this.setTitle(); AppUserStore.focusedState('none'); AppUserStore.focusedState(ScopeMessageList); } @@ -99,7 +99,7 @@ export class MailBoxUserScreen extends AbstractScreen { email === item?.email && item?.count(e.detail) ); */ - this.updateWindowTitle(); + this.setTitle(); }); } diff --git a/dev/Screen/User/Settings.js b/dev/Screen/User/Settings.js index f4d912c7f..60069b128 100644 --- a/dev/Screen/User/Settings.js +++ b/dev/Screen/User/Settings.js @@ -66,11 +66,11 @@ export class SettingsUserScreen extends AbstractSettingsScreen { } onShow() { - this.setSettingsTitle(); + this.setTitle(); keyScope(ScopeSettings); } - setSettingsTitle() { + setTitle() { const sEmail = AccountUserStore.email(); rl.setTitle((sEmail ? sEmail + ' - ' : '') + this.sSettingsTitle); } diff --git a/dev/Stores/User/App.js b/dev/Stores/User/App.js index 9825c8951..3e62de59f 100644 --- a/dev/Stores/User/App.js +++ b/dev/Stores/User/App.js @@ -19,6 +19,7 @@ AppUserStore.focusedState.subscribe(value => { arePopupsVisible() || keyScope(value); ThemeStore.isMobile() && leftPanelDisabled('FolderList' !== value); } - elementById('V-Mail'+name).classList.toggle('focused', name === value); + let el = elementById('V-Mail'+name); + el && el.classList.toggle('focused', name === value); }); }); diff --git a/dev/View/Popup/Contacts.js b/dev/View/Popup/Contacts.js index 15b6d61f3..fa9a8798d 100644 --- a/dev/View/Popup/Contacts.js +++ b/dev/View/Popup/Contacts.js @@ -21,9 +21,10 @@ import { AbstractViewPopup } from 'Knoin/AbstractViews'; import { AskPopupView } from 'View/Popup/Ask'; +import { ScopeContacts } from 'Common/Enums'; + const - CONTACTS_PER_PAGE = 50, - ScopeContacts = 'Contacts'; + CONTACTS_PER_PAGE = 50; let bOpenCompose = false, @@ -342,7 +343,7 @@ export class ContactsPopupView extends AbstractViewPopup { this.search(''); this.contactsCount(0); - ContactUserStore([]); +// ContactUserStore([]); bOpenCompose && showMessageComposer(); } diff --git a/dev/View/User/Contacts/AddressBooks.js b/dev/View/User/Contacts/AddressBooks.js new file mode 100644 index 000000000..32cec1367 --- /dev/null +++ b/dev/View/User/Contacts/AddressBooks.js @@ -0,0 +1,7 @@ +import { AbstractViewLeft } from 'Knoin/AbstractViews'; + +export class AddressBooks extends AbstractViewLeft { + constructor() { + super(); + } +} diff --git a/dev/View/User/Contacts/ContactView.js b/dev/View/User/Contacts/ContactView.js new file mode 100644 index 000000000..fc6d4f5df --- /dev/null +++ b/dev/View/User/Contacts/ContactView.js @@ -0,0 +1,47 @@ +import { AbstractViewRight } from 'Knoin/AbstractViews'; +import { addObservablesTo } from 'External/ko'; +import { decorateKoCommands } from 'Knoin/Knoin'; +import Remote from 'Remote/User/Fetch'; +import { getNotification } from 'Common/Translator'; + +export class ContactView extends AbstractViewRight { + constructor() { + super(); + + addObservablesTo(this, { + contact: null + }); + + this.saveCommand = this.saveCommand.bind(this); + + decorateKoCommands(this, { + saveCommand: self => !self.isBusy() + }); + } + + saveCommand() { + this.saveContact(this.contact()); + } + + saveContact(contact) { + const data = contact.toJSON(); + if (data.jCard != JSON.stringify(contact.jCard)) { + this.isSaving(true); + Remote.request('ContactSave', + (iError, oData) => { + if (iError) { + alert(oData?.ErrorMessage || getNotification(iError)); + } else if (oData.Result.ResultID) { + if (contact.id()) { + contact.id(oData.Result.ResultID); + contact.jCard = JSON.parse(data.jCard); + } else { + this.reloadContactList(); // TODO: remove when e-contact-foreach is dynamic + } + } + this.isSaving(false); + }, data + ); + } + } +} diff --git a/dev/View/User/Contacts/ContactsList.js b/dev/View/User/Contacts/ContactsList.js new file mode 100644 index 000000000..e778fb652 --- /dev/null +++ b/dev/View/User/Contacts/ContactsList.js @@ -0,0 +1,96 @@ +import { AbstractViewRight } from 'Knoin/AbstractViews'; + +import { addObservablesTo, addComputablesTo } from 'External/ko'; +import { computedPaginatorHelper } from 'Common/UtilsUser'; + +import { Selector } from 'Common/Selector'; + +import { SettingsUserStore } from 'Stores/User/Settings'; +import { ContactUserStore } from 'Stores/User/Contact'; + +import { decorateKoCommands } from 'Knoin/Knoin'; + +const + CONTACTS_PER_PAGE = 50; + +export class ContactsList extends AbstractViewRight { + constructor() { + super(); + + addObservablesTo(this, { + search: '', + contactsCount: 0, + + selectorContact: null, + + importButton: null, + + contactsPage: 1, + + isSaving: false, + + contact: null + }); + + addComputablesTo(this, { + checkAll: { + read: () => ContactUserStore.hasChecked(), + write: (value) => { + value = !!value; + ContactUserStore.forEach(contact => contact.checked(value)); + } + } + }); + + this.contacts = ContactUserStore; + + this.useCheckboxesInList = SettingsUserStore.useCheckboxesInList; + + this.selector = new Selector( + ContactUserStore, + this.selectorContact, + null, + '.e-contact-item', + '.e-contact-item .checkboxItem' + ); + + this.selector.on('ItemSelect', contact => this.populateViewContact(contact)); + + this.selector.on('ItemGetUid', contact => contact ? contact.id() : ''); + + addComputablesTo(this, { + contactsPaginator: computedPaginatorHelper( + this.contactsPage, + () => Math.max(1, Math.ceil(this.contactsCount() / CONTACTS_PER_PAGE)) + ), + + contactsCheckedOrSelected: () => { + const checked = ContactUserStore.filter(item => item.checked()), + selected = this.selectorContact(); + return checked.length ? checked : (selected ? [selected] : []); + }, + + contactsSyncEnabled: () => ContactUserStore.allowSync() && ContactUserStore.syncMode(), + + isIncompleteChecked: () => { + const c = ContactUserStore.listChecked().length; + return c && ContactUserStore().length > c; + }, + + isBusy: () => ContactUserStore.syncing() | ContactUserStore.importing() | ContactUserStore.loading() + | this.isSaving() + }); + + this.search.subscribe(() => this.reloadContactList()); + + decorateKoCommands(this, { + deleteCommand: self => !self.isBusy() && 0 < self.contactsCheckedOrSelected().length, + newMessageCommand: self => !self.isBusy() && 0 < self.contactsCheckedOrSelected().length, + syncCommand: self => !self.isBusy() + }); + } + + clearSearch() { + this.search(''); + } +}