Add user editing support to user view model
This commit is contained in:
parent
c04a45f8d3
commit
069eab1f63
@ -356,7 +356,7 @@ button > * {
|
|||||||
border: 1px solid #888;
|
border: 1px solid #888;
|
||||||
}
|
}
|
||||||
|
|
||||||
#user-menu.menu-hidden {
|
#user-menu.menu-hidden, a.menu-hidden {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -91,7 +91,9 @@
|
|||||||
*/
|
*/
|
||||||
private static function exitWithStatus($isError, $extra = NULL) {
|
private static function exitWithStatus($isError, $extra = NULL) {
|
||||||
$output = [];
|
$output = [];
|
||||||
$output["error"] = $isError;
|
if ($isError) {
|
||||||
|
$output["error"] = true;
|
||||||
|
}
|
||||||
if (!empty($extra)) {
|
if (!empty($extra)) {
|
||||||
foreach ($extra as $key => $value) {
|
foreach ($extra as $key => $value) {
|
||||||
$output[$key] = $value;
|
$output[$key] = $value;
|
||||||
|
@ -61,7 +61,7 @@
|
|||||||
<div>
|
<div>
|
||||||
<a data-bind="onShowUserMenu"><img class="icon" alt="<?= $lang['user'] ?>" src="images/user.svg"> <?= htmlspecialchars($auth->user->login) ?></a>
|
<a data-bind="onShowUserMenu"><img class="icon" alt="<?= $lang['user'] ?>" src="images/user.svg"> <?= htmlspecialchars($auth->user->login) ?></a>
|
||||||
<div id="user-menu" class="menu-hidden">
|
<div id="user-menu" class="menu-hidden">
|
||||||
<a id="user-pass"><img class="icon" alt="<?= $lang['changepass'] ?>" src="images/lock.svg"> <?= $lang['changepass'] ?></a>
|
<a id="user-pass" data-bind="onPasswordChange"><img class="icon" alt="<?= $lang['changepass'] ?>" src="images/lock.svg"> <?= $lang['changepass'] ?></a>
|
||||||
<a href="utils/logout.php"><img class="icon" alt="<?= $lang['logout'] ?>" src="images/poweroff.svg"> <?= $lang['logout'] ?></a>
|
<a href="utils/logout.php"><img class="icon" alt="<?= $lang['logout'] ?>" src="images/poweroff.svg"> <?= $lang['logout'] ?></a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -133,10 +133,10 @@
|
|||||||
<div id="admin-menu">
|
<div id="admin-menu">
|
||||||
<div class="menu-title"><?= $lang['adminmenu'] ?></div>
|
<div class="menu-title"><?= $lang['adminmenu'] ?></div>
|
||||||
<?php if ($auth->isAdmin()): ?>
|
<?php if ($auth->isAdmin()): ?>
|
||||||
<a id="adduser" class="menu-link"><?= $lang['adduser'] ?></a>
|
<a id="adduser" class="menu-link" data-bind="onUserAdd"><?= $lang['adduser'] ?></a>
|
||||||
<a id="edituser" class="menu-link"><?= $lang['edituser'] ?></a>
|
<a id="edituser" class="menu-link" data-bind="onUserEdit"><?= $lang['edituser'] ?></a>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
<a id="edittrack" class="menu-link"><?= $lang['edittrack'] ?></a>
|
<a id="edittrack" class="menu-link" data-bind="onTrackEdit"><?= $lang['edittrack'] ?></a>
|
||||||
</div>
|
</div>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
|
|
||||||
|
@ -18,6 +18,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { lang as $, auth } from './initializer.js';
|
import { lang as $, auth } from './initializer.js';
|
||||||
|
import UserDialogModel from './userdialogmodel.js';
|
||||||
import ViewModel from './viewmodel.js';
|
import ViewModel from './viewmodel.js';
|
||||||
import uSelect from './select.js';
|
import uSelect from './select.js';
|
||||||
import uUser from './user.js';
|
import uUser from './user.js';
|
||||||
@ -36,14 +37,29 @@ export default class UserViewModel extends ViewModel {
|
|||||||
/** @type {uUser[]} */
|
/** @type {uUser[]} */
|
||||||
userList: [],
|
userList: [],
|
||||||
/** @type {string} */
|
/** @type {string} */
|
||||||
currentUserId: '0'
|
currentUserId: '0',
|
||||||
|
// click handlers
|
||||||
|
/** @type {function} */
|
||||||
|
onUserEdit: null,
|
||||||
|
/** @type {function} */
|
||||||
|
onUserAdd: null,
|
||||||
|
/** @type {function} */
|
||||||
|
onPasswordChange: null
|
||||||
});
|
});
|
||||||
|
this.setClickHandlers();
|
||||||
/** @type HTMLSelectElement */
|
/** @type HTMLSelectElement */
|
||||||
const listEl = document.querySelector('#user');
|
const listEl = document.querySelector('#user');
|
||||||
|
this.editEl = this.getBoundElement('onUserEdit');
|
||||||
this.select = new uSelect(listEl, $._('suser'), `- ${$._('allusers')} -`);
|
this.select = new uSelect(listEl, $._('suser'), `- ${$._('allusers')} -`);
|
||||||
this.state = state;
|
this.state = state;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setClickHandlers() {
|
||||||
|
this.model.onUserEdit = () => this.showDialog('edit');
|
||||||
|
this.model.onUserAdd = () => this.showDialog('add');
|
||||||
|
this.model.onPasswordChange = () => this.showDialog('pass');
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return {UserViewModel}
|
* @return {UserViewModel}
|
||||||
*/
|
*/
|
||||||
@ -78,6 +94,7 @@ export default class UserViewModel extends ViewModel {
|
|||||||
this.onChanged('currentUserId', (listValue) => {
|
this.onChanged('currentUserId', (listValue) => {
|
||||||
this.state.showAllUsers = listValue === uSelect.allValue;
|
this.state.showAllUsers = listValue === uSelect.allValue;
|
||||||
this.state.currentUser = this.model.userList.find((_user) => _user.listValue === listValue) || null;
|
this.state.currentUser = this.model.userList.find((_user) => _user.listValue === listValue) || null;
|
||||||
|
UserViewModel.setMenuVisible(this.editEl, this.state.currentUser !== null && !this.state.currentUser.isEqualTo(auth.user));
|
||||||
});
|
});
|
||||||
state.onChanged('showLatest', (showLatest) => {
|
state.onChanged('showLatest', (showLatest) => {
|
||||||
if (showLatest) {
|
if (showLatest) {
|
||||||
@ -88,4 +105,38 @@ export default class UserViewModel extends ViewModel {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
showDialog(action) {
|
||||||
|
const vm = new UserDialogModel(this, action);
|
||||||
|
vm.init();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {uUser} newUser
|
||||||
|
*/
|
||||||
|
onUserAdded(newUser) {
|
||||||
|
this.model.userList.push(newUser);
|
||||||
|
this.model.userList.sort((a, b) => ((a.login > b.login) ? 1 : -1));
|
||||||
|
}
|
||||||
|
|
||||||
|
onUserDeleted() {
|
||||||
|
const index = this.model.userList.indexOf(this.state.currentUser);
|
||||||
|
this.state.currentUser = null;
|
||||||
|
if (index !== -1) {
|
||||||
|
this.model.userList.splice(index, 1);
|
||||||
|
if (this.model.userList.length) {
|
||||||
|
this.model.currentUserId = this.model.userList[index].listValue;
|
||||||
|
} else {
|
||||||
|
this.model.currentUserId = '0';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static setMenuVisible(el, visible) {
|
||||||
|
if (visible) {
|
||||||
|
el.classList.remove('menu-hidden');
|
||||||
|
} else {
|
||||||
|
el.classList.add('menu-hidden');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
8
js/test/fixtures/main-authorized.html
vendored
8
js/test/fixtures/main-authorized.html
vendored
@ -5,7 +5,7 @@
|
|||||||
<div>
|
<div>
|
||||||
<a data-bind="onShowUserMenu"><img class="icon" alt="User" src="images/user.svg"> testUser</a>
|
<a data-bind="onShowUserMenu"><img class="icon" alt="User" src="images/user.svg"> testUser</a>
|
||||||
<div id="user-menu" class="menu-hidden">
|
<div id="user-menu" class="menu-hidden">
|
||||||
<a id="user-pass"><img class="icon" alt="Change password" src="images/lock.svg"> Change password</a>
|
<a id="user-pass" data-bind="onPasswordChange"><img class="icon" alt="Change password" src="images/lock.svg"> Change password</a>
|
||||||
<a href="utils/logout.php"><img class="icon" alt="Log out" src="images/poweroff.svg"> Log out</a>
|
<a href="utils/logout.php"><img class="icon" alt="Log out" src="images/poweroff.svg"> Log out</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -80,9 +80,9 @@
|
|||||||
|
|
||||||
<div id="admin-menu">
|
<div id="admin-menu">
|
||||||
<div class="menu-title">Administration</div>
|
<div class="menu-title">Administration</div>
|
||||||
<a id="adduser" class="menu-link">Add user</a>
|
<a id="adduser" class="menu-link" data-bind="onUserAdd">Add user</a>
|
||||||
<a id="edituser" class="menu-link">Edit user</a>
|
<a id="edituser" class="menu-link" data-bind="onUserEdit">Edit user</a>
|
||||||
<a id="edittrack" class="menu-link">Edit track</a>
|
<a id="edittrack" class="menu-link" data-bind="onTrackEdit">Edit track</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
@ -33,16 +33,22 @@ describe('UserViewModel tests', () => {
|
|||||||
let users;
|
let users;
|
||||||
/** @type {HTMLSelectElement} */
|
/** @type {HTMLSelectElement} */
|
||||||
let userEl;
|
let userEl;
|
||||||
|
let userEditEl;
|
||||||
|
let userAddEl;
|
||||||
|
let userPassEl;
|
||||||
let vm;
|
let vm;
|
||||||
|
|
||||||
beforeEach((done) => {
|
beforeEach((done) => {
|
||||||
Fixture.load('main.html')
|
Fixture.load('main-authorized.html')
|
||||||
.then(() => done())
|
.then(() => done())
|
||||||
.catch((e) => done.fail(e));
|
.catch((e) => done.fail(e));
|
||||||
});
|
});
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
userEl = document.querySelector('#user');
|
userEl = document.querySelector('#user');
|
||||||
|
userEditEl = document.querySelector('#edituser');
|
||||||
|
userAddEl = document.querySelector('#adduser');
|
||||||
|
userPassEl = document.querySelector('#user-pass');
|
||||||
config.reinitialize();
|
config.reinitialize();
|
||||||
lang.init(config);
|
lang.init(config);
|
||||||
lang.strings['suser'] = 'select user';
|
lang.strings['suser'] = 'select user';
|
||||||
@ -211,4 +217,100 @@ describe('UserViewModel tests', () => {
|
|||||||
}, 100);
|
}, 100);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should show user edit dialog on button click', (done) => {
|
||||||
|
// given
|
||||||
|
spyOn(vm, 'showDialog');
|
||||||
|
// when
|
||||||
|
vm.bindAll();
|
||||||
|
userEditEl.click();
|
||||||
|
// then
|
||||||
|
setTimeout(() => {
|
||||||
|
expect(vm.showDialog).toHaveBeenCalledWith('edit');
|
||||||
|
done();
|
||||||
|
}, 100);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should show user add dialog on button click', (done) => {
|
||||||
|
// given
|
||||||
|
spyOn(vm, 'showDialog');
|
||||||
|
// when
|
||||||
|
vm.bindAll();
|
||||||
|
userAddEl.click();
|
||||||
|
// then
|
||||||
|
setTimeout(() => {
|
||||||
|
expect(vm.showDialog).toHaveBeenCalledWith('add');
|
||||||
|
done();
|
||||||
|
}, 100);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should show password change dialog on button click', (done) => {
|
||||||
|
// given
|
||||||
|
spyOn(vm, 'showDialog');
|
||||||
|
// when
|
||||||
|
vm.bindAll();
|
||||||
|
userPassEl.click();
|
||||||
|
// then
|
||||||
|
setTimeout(() => {
|
||||||
|
expect(vm.showDialog).toHaveBeenCalledWith('pass');
|
||||||
|
done();
|
||||||
|
}, 100);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should add new user to user list in alphabetic order', () => {
|
||||||
|
// given
|
||||||
|
user1 = new uUser(1, 'b');
|
||||||
|
user2 = new uUser(2, 'a');
|
||||||
|
vm.model.userList = [ user1 ];
|
||||||
|
// when
|
||||||
|
vm.onUserAdded(user2);
|
||||||
|
// then
|
||||||
|
expect(vm.model.userList.length).toBe(2);
|
||||||
|
expect(vm.model.userList[0]).toBe(user2);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should remove current user from user list and set new current user id', () => {
|
||||||
|
// given
|
||||||
|
vm.model.userList = [ user1, user2 ];
|
||||||
|
vm.state.currentUser = user1;
|
||||||
|
vm.model.currentUserId = user1.listValue;
|
||||||
|
// when
|
||||||
|
vm.onUserDeleted();
|
||||||
|
// then
|
||||||
|
expect(vm.model.userList.length).toBe(1);
|
||||||
|
expect(vm.model.currentUserId).toBe(user2.listValue);
|
||||||
|
expect(vm.state.currentUser).toBe(null);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should remove last remaining element from user list and set empty user id', () => {
|
||||||
|
// given
|
||||||
|
vm.model.userList = [ user1 ];
|
||||||
|
vm.state.currentUser = user1;
|
||||||
|
vm.model.currentUserId = user1.listValue;
|
||||||
|
// when
|
||||||
|
vm.onUserDeleted();
|
||||||
|
// then
|
||||||
|
expect(vm.model.userList.length).toBe(0);
|
||||||
|
expect(vm.model.currentUserId).toBe('0');
|
||||||
|
expect(vm.state.currentUser).toBe(null);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('show hide element', () => {
|
||||||
|
// given
|
||||||
|
const element = document.createElement('div');
|
||||||
|
// when
|
||||||
|
UserViewModel.setMenuVisible(element, false);
|
||||||
|
// then
|
||||||
|
expect(element.classList.contains('menu-hidden')).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('show shown hidden element', () => {
|
||||||
|
// given
|
||||||
|
const element = document.createElement('div');
|
||||||
|
element.classList.add('menu-hidden');
|
||||||
|
// when
|
||||||
|
UserViewModel.setMenuVisible(element, true);
|
||||||
|
// then
|
||||||
|
expect(element.classList.contains('menu-hidden')).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
@ -30,7 +30,7 @@
|
|||||||
|
|
||||||
$lang = (new uLang(uConfig::$lang))->getStrings();
|
$lang = (new uLang(uConfig::$lang))->getStrings();
|
||||||
|
|
||||||
if (!$auth->isAuthenticated() || !$auth->isAdmin() || $auth->user->login == $login || empty($action) || empty($login)) {
|
if (!$auth->isAuthenticated() || !$auth->isAdmin() || $auth->user->login === $login || empty($action) || empty($login)) {
|
||||||
uUtils::exitWithError($lang["servererror"]);
|
uUtils::exitWithError($lang["servererror"]);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -45,7 +45,7 @@
|
|||||||
if (empty($pass) || ($userId = uUser::add($login, $pass)) === false) {
|
if (empty($pass) || ($userId = uUser::add($login, $pass)) === false) {
|
||||||
uUtils::exitWithError($lang["servererror"]);
|
uUtils::exitWithError($lang["servererror"]);
|
||||||
} else {
|
} else {
|
||||||
$data = [ 'userid' => $userId ];
|
$data = [ 'id' => $userId ];
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user