favorite modules positions
This commit is contained in:
parent
a594e63566
commit
9f00fd11d2
|
@ -12,17 +12,23 @@ module.exports = function(Self) {
|
|||
}
|
||||
});
|
||||
|
||||
Self.getStarredModules = async ctx => {
|
||||
Self.getStarredModules = async(ctx, options) => {
|
||||
const models = Self.app.models;
|
||||
const userId = ctx.req.accessToken.userId;
|
||||
|
||||
let myOptions = {};
|
||||
|
||||
if (typeof options == 'object')
|
||||
Object.assign(myOptions, options);
|
||||
|
||||
const filter = {
|
||||
where: {
|
||||
workerFk: userId
|
||||
},
|
||||
fields: ['moduleFk']
|
||||
fields: ['id', 'workerFk', 'moduleFk', 'position'],
|
||||
order: 'position ASC'
|
||||
};
|
||||
|
||||
const starredModules = await Self.app.models.StarredModule.find(filter);
|
||||
|
||||
return starredModules;
|
||||
return models.StarredModule.find(filter, myOptions);
|
||||
};
|
||||
};
|
||||
|
|
|
@ -0,0 +1,98 @@
|
|||
module.exports = function(Self) {
|
||||
Self.remoteMethodCtx('setPosition', {
|
||||
description: 'sets the position of a given module',
|
||||
accessType: 'WRITE',
|
||||
returns: {
|
||||
type: 'object',
|
||||
root: true
|
||||
},
|
||||
accepts: [
|
||||
{
|
||||
arg: 'moduleName',
|
||||
type: 'string',
|
||||
required: true,
|
||||
description: 'The module name'
|
||||
},
|
||||
{
|
||||
arg: 'action',
|
||||
type: 'string',
|
||||
required: true,
|
||||
description: 'Whether to increases or decreases the module position, last if undefined'
|
||||
}
|
||||
],
|
||||
http: {
|
||||
path: `/setPosition`,
|
||||
verb: 'post'
|
||||
}
|
||||
});
|
||||
|
||||
Self.setPosition = async(ctx, moduleName, action, options) => {
|
||||
const models = Self.app.models;
|
||||
const userId = ctx.req.accessToken.userId;
|
||||
|
||||
let tx;
|
||||
let myOptions = {};
|
||||
|
||||
if (typeof options == 'object')
|
||||
Object.assign(myOptions, options);
|
||||
|
||||
if (!myOptions.transaction) {
|
||||
tx = await Self.beginTransaction({});
|
||||
myOptions.transaction = tx;
|
||||
}
|
||||
|
||||
try {
|
||||
const filter = {
|
||||
where: {
|
||||
workerFk: userId,
|
||||
moduleFk: moduleName
|
||||
},
|
||||
order: 'position DESC'
|
||||
};
|
||||
|
||||
const [movingModule] = await models.StarredModule.find(filter, myOptions);
|
||||
|
||||
let operator;
|
||||
let order;
|
||||
|
||||
switch (action) {
|
||||
case 'increase':
|
||||
operator = {lt: movingModule.position};
|
||||
order = 'position DESC';
|
||||
break;
|
||||
case 'decrease':
|
||||
operator = {gt: movingModule.position};
|
||||
order = 'position ASC';
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
const pushedModule = await models.StarredModule.findOne({
|
||||
where: {
|
||||
position: operator,
|
||||
workerFk: userId
|
||||
},
|
||||
order: order
|
||||
}, myOptions);
|
||||
|
||||
if (!pushedModule) return;
|
||||
|
||||
const movingPosition = pushedModule.position;
|
||||
const pushingPosition = movingModule.position;
|
||||
|
||||
await movingModule.updateAttribute('position', movingPosition, myOptions);
|
||||
await pushedModule.updateAttribute('position', pushingPosition, myOptions);
|
||||
|
||||
if (tx) await tx.commit();
|
||||
|
||||
return {
|
||||
movingModule: movingModule,
|
||||
pushedModule: pushedModule
|
||||
};
|
||||
} catch (e) {
|
||||
if (tx) await tx.rollback();
|
||||
throw e;
|
||||
}
|
||||
};
|
||||
};
|
|
@ -19,7 +19,7 @@ describe('getStarredModules()', () => {
|
|||
});
|
||||
|
||||
it(`should return the starred modules for a given user`, async() => {
|
||||
const newStarred = await app.models.StarredModule.create({workerFk: 9, moduleFk: 'Clients'});
|
||||
const newStarred = await app.models.StarredModule.create({workerFk: 9, moduleFk: 'Clients', position: 1});
|
||||
const starredModules = await app.models.StarredModule.getStarredModules(ctx);
|
||||
|
||||
expect(starredModules.length).toEqual(1);
|
||||
|
|
|
@ -0,0 +1,223 @@
|
|||
const app = require('vn-loopback/server/server');
|
||||
const LoopBackContext = require('loopback-context');
|
||||
|
||||
describe('setPosition()', () => {
|
||||
const activeCtx = {
|
||||
accessToken: {userId: 9},
|
||||
http: {
|
||||
req: {
|
||||
headers: {origin: 'http://localhost'}
|
||||
}
|
||||
}
|
||||
};
|
||||
const ctx = {
|
||||
req: activeCtx
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
spyOn(LoopBackContext, 'getCurrentContext').and.returnValue({
|
||||
active: activeCtx
|
||||
});
|
||||
});
|
||||
|
||||
it('should increase the orders module position by replacing it with clients and vice versa', async() => {
|
||||
const tx = await app.models.StarredModule.beginTransaction({});
|
||||
|
||||
const filter = {
|
||||
where: {
|
||||
workerFk: ctx.req.accessToken.userId,
|
||||
moduleFk: 'Orders'
|
||||
}
|
||||
};
|
||||
|
||||
try {
|
||||
const options = {transaction: tx};
|
||||
await app.models.StarredModule.toggleStarredModule(ctx, 'Orders', options);
|
||||
await app.models.StarredModule.toggleStarredModule(ctx, 'Clients', options);
|
||||
|
||||
let orders = await app.models.StarredModule.findOne(filter, options);
|
||||
|
||||
filter.where.moduleFk = 'Clients';
|
||||
let clients = await app.models.StarredModule.findOne(filter, options);
|
||||
|
||||
expect(orders.position).toEqual(1);
|
||||
expect(clients.position).toEqual(2);
|
||||
|
||||
await app.models.StarredModule.setPosition(ctx, 'Clients', 'increase', options);
|
||||
|
||||
filter.where.moduleFk = 'Clients';
|
||||
clients = await app.models.StarredModule.findOne(filter, options);
|
||||
|
||||
filter.where.moduleFk = 'Orders';
|
||||
orders = await app.models.StarredModule.findOne(filter, options);
|
||||
|
||||
expect(clients.position).toEqual(1);
|
||||
expect(orders.position).toEqual(2);
|
||||
|
||||
await tx.rollback();
|
||||
} catch (e) {
|
||||
await tx.rollback();
|
||||
throw e;
|
||||
}
|
||||
});
|
||||
|
||||
it('should decrease the orders module position by replacing it with clients and vice versa', async() => {
|
||||
const tx = await app.models.StarredModule.beginTransaction({});
|
||||
|
||||
const filter = {
|
||||
where: {
|
||||
workerFk: ctx.req.accessToken.userId,
|
||||
moduleFk: 'Orders'
|
||||
}
|
||||
};
|
||||
|
||||
try {
|
||||
const options = {transaction: tx};
|
||||
await app.models.StarredModule.toggleStarredModule(ctx, 'Orders', options);
|
||||
await app.models.StarredModule.toggleStarredModule(ctx, 'Clients', options);
|
||||
|
||||
let orders = await app.models.StarredModule.findOne(filter, options);
|
||||
|
||||
filter.where.moduleFk = 'Clients';
|
||||
let clients = await app.models.StarredModule.findOne(filter, options);
|
||||
|
||||
expect(orders.position).toEqual(1);
|
||||
expect(clients.position).toEqual(2);
|
||||
|
||||
await app.models.StarredModule.setPosition(ctx, 'Orders', 'decrease', options);
|
||||
|
||||
filter.where.moduleFk = 'Orders';
|
||||
orders = await app.models.StarredModule.findOne(filter, options);
|
||||
|
||||
filter.where.moduleFk = 'Clients';
|
||||
clients = await app.models.StarredModule.findOne(filter, options);
|
||||
|
||||
expect(orders.position).toEqual(2);
|
||||
expect(clients.position).toEqual(1);
|
||||
|
||||
await tx.rollback();
|
||||
} catch (e) {
|
||||
await tx.rollback();
|
||||
throw e;
|
||||
}
|
||||
});
|
||||
|
||||
it('should switch two modules after adding and deleting several modules', async() => {
|
||||
const tx = await app.models.StarredModule.beginTransaction({});
|
||||
|
||||
const filter = {
|
||||
where: {
|
||||
workerFk: ctx.req.accessToken.userId,
|
||||
moduleFk: 'Items'
|
||||
}
|
||||
};
|
||||
|
||||
try {
|
||||
const options = {transaction: tx};
|
||||
|
||||
await app.models.StarredModule.toggleStarredModule(ctx, 'Clients', options);
|
||||
await app.models.StarredModule.toggleStarredModule(ctx, 'Orders', options);
|
||||
await app.models.StarredModule.toggleStarredModule(ctx, 'Clients', options);
|
||||
await app.models.StarredModule.toggleStarredModule(ctx, 'Orders', options);
|
||||
await app.models.StarredModule.toggleStarredModule(ctx, 'Items', options);
|
||||
await app.models.StarredModule.toggleStarredModule(ctx, 'Claims', options);
|
||||
await app.models.StarredModule.toggleStarredModule(ctx, 'Clients', options);
|
||||
await app.models.StarredModule.toggleStarredModule(ctx, 'Orders', options);
|
||||
await app.models.StarredModule.toggleStarredModule(ctx, 'Zones', options);
|
||||
|
||||
const items = await app.models.StarredModule.findOne(filter, options);
|
||||
|
||||
filter.where.moduleFk = 'Claims';
|
||||
const claims = await app.models.StarredModule.findOne(filter, options);
|
||||
|
||||
filter.where.moduleFk = 'Clients';
|
||||
let clients = await app.models.StarredModule.findOne(filter, options);
|
||||
|
||||
filter.where.moduleFk = 'Orders';
|
||||
let orders = await app.models.StarredModule.findOne(filter, options);
|
||||
|
||||
filter.where.moduleFk = 'Zones';
|
||||
const zones = await app.models.StarredModule.findOne(filter, options);
|
||||
|
||||
expect(items.position).toEqual(1);
|
||||
expect(claims.position).toEqual(2);
|
||||
expect(clients.position).toEqual(3);
|
||||
expect(orders.position).toEqual(4);
|
||||
expect(zones.position).toEqual(5);
|
||||
|
||||
await app.models.StarredModule.setPosition(ctx, 'Clients', 'decrease', options);
|
||||
|
||||
filter.where.moduleFk = 'Orders';
|
||||
orders = await app.models.StarredModule.findOne(filter, options);
|
||||
|
||||
filter.where.moduleFk = 'Clients';
|
||||
clients = await app.models.StarredModule.findOne(filter, options);
|
||||
|
||||
expect(orders.position).toEqual(3);
|
||||
expect(clients.position).toEqual(4);
|
||||
|
||||
await tx.rollback();
|
||||
} catch (e) {
|
||||
await tx.rollback();
|
||||
throw e;
|
||||
}
|
||||
});
|
||||
|
||||
it('should switch two modules after adding and deleting a module between them', async() => {
|
||||
const tx = await app.models.StarredModule.beginTransaction({});
|
||||
|
||||
const filter = {
|
||||
where: {
|
||||
workerFk: ctx.req.accessToken.userId,
|
||||
moduleFk: 'Items'
|
||||
}
|
||||
};
|
||||
|
||||
try {
|
||||
const options = {transaction: tx};
|
||||
|
||||
await app.models.StarredModule.toggleStarredModule(ctx, 'Items', options);
|
||||
await app.models.StarredModule.toggleStarredModule(ctx, 'Clients', options);
|
||||
await app.models.StarredModule.toggleStarredModule(ctx, 'Claims', options);
|
||||
await app.models.StarredModule.toggleStarredModule(ctx, 'Orders', options);
|
||||
await app.models.StarredModule.toggleStarredModule(ctx, 'Zones', options);
|
||||
|
||||
const items = await app.models.StarredModule.findOne(filter, options);
|
||||
|
||||
filter.where.moduleFk = 'Clients';
|
||||
let clients = await app.models.StarredModule.findOne(filter, options);
|
||||
|
||||
filter.where.moduleFk = 'Claims';
|
||||
const claims = await app.models.StarredModule.findOne(filter, options);
|
||||
|
||||
filter.where.moduleFk = 'Orders';
|
||||
let orders = await app.models.StarredModule.findOne(filter, options);
|
||||
|
||||
filter.where.moduleFk = 'Zones';
|
||||
const zones = await app.models.StarredModule.findOne(filter, options);
|
||||
|
||||
expect(items.position).toEqual(1);
|
||||
expect(clients.position).toEqual(2);
|
||||
expect(claims.position).toEqual(3);
|
||||
expect(orders.position).toEqual(4);
|
||||
expect(zones.position).toEqual(5);
|
||||
|
||||
await app.models.StarredModule.toggleStarredModule(ctx, 'Claims', options);
|
||||
await app.models.StarredModule.setPosition(ctx, 'Clients', 'decrease', options);
|
||||
|
||||
filter.where.moduleFk = 'Clients';
|
||||
clients = await app.models.StarredModule.findOne(filter, options);
|
||||
|
||||
filter.where.moduleFk = 'Orders';
|
||||
orders = await app.models.StarredModule.findOne(filter, options);
|
||||
|
||||
expect(orders.position).toEqual(2);
|
||||
expect(clients.position).toEqual(4);
|
||||
|
||||
await tx.rollback();
|
||||
} catch (e) {
|
||||
await tx.rollback();
|
||||
throw e;
|
||||
}
|
||||
});
|
||||
});
|
|
@ -27,6 +27,7 @@ describe('toggleStarredModule()', () => {
|
|||
expect(starredModules.length).toEqual(1);
|
||||
expect(starredModule.moduleFk).toEqual('Orders');
|
||||
expect(starredModule.workerFk).toEqual(activeCtx.accessToken.userId);
|
||||
expect(starredModule.position).toEqual(starredModules.length);
|
||||
|
||||
await app.models.StarredModule.toggleStarredModule(ctx, 'Orders');
|
||||
starredModules = await app.models.StarredModule.getStarredModules(ctx);
|
||||
|
|
|
@ -18,24 +18,61 @@ module.exports = function(Self) {
|
|||
}
|
||||
});
|
||||
|
||||
Self.toggleStarredModule = async(ctx, moduleName) => {
|
||||
Self.toggleStarredModule = async(ctx, moduleName, options) => {
|
||||
const models = Self.app.models;
|
||||
const userId = ctx.req.accessToken.userId;
|
||||
const filter = {
|
||||
where: {
|
||||
workerFk: userId,
|
||||
moduleFk: moduleName
|
||||
|
||||
let tx;
|
||||
let myOptions = {};
|
||||
|
||||
if (typeof options == 'object')
|
||||
Object.assign(myOptions, options);
|
||||
|
||||
if (!myOptions.transaction) {
|
||||
tx = await Self.beginTransaction({});
|
||||
myOptions.transaction = tx;
|
||||
}
|
||||
|
||||
try {
|
||||
const filter = {
|
||||
where: {
|
||||
workerFk: userId,
|
||||
moduleFk: moduleName
|
||||
}
|
||||
};
|
||||
|
||||
const [starredModule] = await models.StarredModule.find(filter, myOptions);
|
||||
|
||||
delete filter.moduleName;
|
||||
const allStarredModules = await models.StarredModule.getStarredModules(ctx, myOptions);
|
||||
|
||||
let addedModule;
|
||||
|
||||
if (starredModule)
|
||||
await starredModule.destroy(myOptions);
|
||||
else {
|
||||
let highestPosition;
|
||||
if (allStarredModules.length) {
|
||||
allStarredModules.sort((a, b) => {
|
||||
return a.position - b.position;
|
||||
});
|
||||
highestPosition = allStarredModules[allStarredModules.length - 1].position + 1;
|
||||
} else
|
||||
highestPosition = 1;
|
||||
|
||||
addedModule = await models.StarredModule.create({
|
||||
workerFk: userId,
|
||||
moduleFk: moduleName,
|
||||
position: highestPosition
|
||||
}, myOptions);
|
||||
}
|
||||
};
|
||||
|
||||
const [starredModule] = await Self.app.models.StarredModule.find(filter);
|
||||
if (tx) await tx.commit();
|
||||
|
||||
if (starredModule)
|
||||
await starredModule.destroy();
|
||||
else {
|
||||
return Self.app.models.StarredModule.create({
|
||||
workerFk: userId,
|
||||
moduleFk: moduleName
|
||||
});
|
||||
return addedModule;
|
||||
} catch (e) {
|
||||
if (tx) await tx.rollback();
|
||||
throw e;
|
||||
}
|
||||
};
|
||||
};
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
module.exports = Self => {
|
||||
require('../methods/starred-module/getStarredModules')(Self);
|
||||
require('../methods/starred-module/toggleStarredModule')(Self);
|
||||
require('../methods/starred-module/setPosition')(Self);
|
||||
};
|
||||
|
|
|
@ -18,6 +18,10 @@
|
|||
"moduleFk": {
|
||||
"type": "string",
|
||||
"required": true
|
||||
},
|
||||
"position": {
|
||||
"type": "number",
|
||||
"required": true
|
||||
}
|
||||
},
|
||||
"relations": {
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
ALTER TABLE `vn`.`starredModule`
|
||||
ADD `position` INT NOT NULL AFTER `moduleFk`;
|
|
@ -9,18 +9,36 @@
|
|||
<vn-icon icon="push_pin"></vn-icon>
|
||||
</div>
|
||||
<div class="modules">
|
||||
<a
|
||||
ng-repeat="mod in ::$ctrl.modules"
|
||||
<a
|
||||
ng-repeat="mod in $ctrl.modules | orderBy: '+position'"
|
||||
ng-animate-ref="{{mod.position}}"
|
||||
ng-if='mod.starred'
|
||||
ui-sref="{{::mod.route.state}}"
|
||||
translate-attr="{title: mod.name}"
|
||||
class="vn-shadow">
|
||||
<div
|
||||
vn-tooltip="Remove from favorites"
|
||||
class="pin"
|
||||
ng-click="$ctrl.toggleStarredModule(mod, $event)">
|
||||
<vn-icon icon="remove_circle"></vn-icon>
|
||||
</div>
|
||||
<span>
|
||||
<div
|
||||
vn-tooltip="Move left"
|
||||
class="small-icon"
|
||||
ng-class="{hidden: $index === 0}"
|
||||
ng-click="$ctrl.moveModule(mod, $event, 'increase')">
|
||||
<vn-icon icon="arrow_left"></vn-icon>
|
||||
</div>
|
||||
<div
|
||||
vn-tooltip="Remove from favorites"
|
||||
class="small-icon"
|
||||
ng-click="$ctrl.toggleStarredModule(mod, $event)">
|
||||
<vn-icon icon="remove_circle"></vn-icon>
|
||||
</div>
|
||||
<div
|
||||
vn-tooltip="Move right"
|
||||
class="small-icon"
|
||||
ng-class="{hidden: $ctrl.starredCount == $index + 1}"
|
||||
ng-click="$ctrl.moveModule(mod, $event, 'decrease')">
|
||||
<vn-icon icon="arrow_right"></vn-icon>
|
||||
</div>
|
||||
in: {{$index}} - pos {{mod.position}}
|
||||
</span>
|
||||
<div>
|
||||
<vn-icon icon="{{::mod.icon || 'photo'}}"></vn-icon>
|
||||
</div>
|
||||
|
@ -41,17 +59,19 @@
|
|||
</div>
|
||||
<div class="modules">
|
||||
<a
|
||||
ng-repeat="mod in ::$ctrl.modules"
|
||||
ng-repeat="mod in $ctrl.modules"
|
||||
ng-if='!mod.starred'
|
||||
ui-sref="{{::mod.route.state}}"
|
||||
translate-attr="{title: mod.name}"
|
||||
class="vn-shadow">
|
||||
<div
|
||||
vn-tooltip="Add to favorites"
|
||||
class="pin"
|
||||
ng-click="$ctrl.toggleStarredModule(mod, $event)">
|
||||
<vn-icon icon="push_pin"></vn-icon>
|
||||
</div>
|
||||
<span>
|
||||
<div
|
||||
vn-tooltip="Add to favorites"
|
||||
class="small-icon"
|
||||
ng-click="$ctrl.toggleStarredModule(mod, $event)">
|
||||
<vn-icon icon="push_pin"></vn-icon>
|
||||
</div>
|
||||
</span>
|
||||
<div>
|
||||
<vn-icon icon="{{::mod.icon || 'photo'}}"></vn-icon>
|
||||
</div>
|
||||
|
|
|
@ -35,6 +35,7 @@ export default class Controller extends Component {
|
|||
for (let starredModule of res.data) {
|
||||
const module = this.modules.find(mod => mod.name === starredModule.moduleFk);
|
||||
module.starred = true;
|
||||
module.position = starredModule.position;
|
||||
}
|
||||
this.countModules();
|
||||
});
|
||||
|
@ -48,9 +49,10 @@ export default class Controller extends Component {
|
|||
const params = {moduleName: module.name};
|
||||
const query = `starredModules/toggleStarredModule`;
|
||||
this.$http.post(query, params).then(res => {
|
||||
if (res.data)
|
||||
if (res.data) {
|
||||
module.starred = true;
|
||||
else
|
||||
module.position = res.data.position;
|
||||
} else
|
||||
module.starred = false;
|
||||
|
||||
this.vnApp.showSuccess(this.$t('Data saved!'));
|
||||
|
@ -74,6 +76,26 @@ export default class Controller extends Component {
|
|||
|
||||
return this.$sce.trustAsHtml(getName(mod));
|
||||
}
|
||||
|
||||
moveModule(module, event, action) {
|
||||
if (event.defaultPrevented) return;
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
|
||||
const params = {moduleName: module.name, action: action};
|
||||
const query = `starredModules/setPosition`;
|
||||
this.$http.post(query, params).then(res => {
|
||||
if (res.data) {
|
||||
module.position = res.data.movingModule.position;
|
||||
this.modules.forEach(mod => {
|
||||
if (mod.name == res.data.pushedModule.moduleFk)
|
||||
mod.position = res.data.pushedModule.position;
|
||||
});
|
||||
this.vnApp.showSuccess(this.$t('Data saved!'));
|
||||
this.countModules();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
Controller.$inject = ['$element', '$scope', 'vnModules', '$sce'];
|
||||
|
||||
|
|
|
@ -66,19 +66,21 @@ vn-home {
|
|||
background-color: $color-button;
|
||||
color: $color-font-dark;
|
||||
|
||||
& .pin {
|
||||
& .small-icon {
|
||||
display: inline-flex;
|
||||
opacity: 0;
|
||||
flex-direction: row;
|
||||
justify-content: left;
|
||||
height: 20px;
|
||||
width: 20px;
|
||||
vn-icon {
|
||||
margin: auto;
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
}
|
||||
&:hover .pin {
|
||||
&:hover .small-icon {
|
||||
flex-direction: row;
|
||||
opacity: 1;
|
||||
|
||||
}
|
||||
& .hidden {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
& > div {
|
||||
|
|
Loading…
Reference in New Issue