First working version
gitea/salix/1838-searchbar_refactor There was a failure building this commit Details

This commit is contained in:
Juan Ferrer 2019-11-10 11:08:44 +01:00
parent 026c608f42
commit 515234187a
229 changed files with 3278 additions and 2925 deletions

View File

@ -346,7 +346,7 @@ let actions = {
.write('vn-searchbar input', searchValue) .write('vn-searchbar input', searchValue)
.click('vn-searchbar vn-icon[icon="search"]') .click('vn-searchbar vn-icon[icon="search"]')
.wait(100) .wait(100)
.waitForNumberOfElements('.searchResult', 1) .waitForNumberOfElements('.search-result', 1)
.evaluate(() => { .evaluate(() => {
return document.querySelector('ui-view vn-card vn-table') != null; return document.querySelector('ui-view vn-card vn-table') != null;
}) })

View File

@ -23,7 +23,7 @@ export default {
clientsIndex: { clientsIndex: {
searchClientInput: `vn-textfield input`, searchClientInput: `vn-textfield input`,
searchButton: 'vn-searchbar vn-icon[icon="search"]', searchButton: 'vn-searchbar vn-icon[icon="search"]',
searchResult: 'vn-client-index .vn-list-item', search-result: 'vn-client-index .vn-item',
createClientButton: `vn-float-button`, createClientButton: `vn-float-button`,
othersButton: 'vn-left-menu li[name="Others"] > a' othersButton: 'vn-left-menu li[name="Others"] > a'
}, },
@ -182,9 +182,9 @@ export default {
itemsIndex: { itemsIndex: {
searchIcon: 'vn-item-index vn-searchbar vn-icon[icon="search"]', searchIcon: 'vn-item-index vn-searchbar vn-icon[icon="search"]',
createItemButton: `vn-float-button`, createItemButton: `vn-float-button`,
searchResult: 'vn-item-index a.vn-tr', search-result: 'vn-item-index a.vn-tr',
searchResultPreviewButton: 'vn-item-index .buttons > [icon="desktop_windows"]', search-resultPreviewButton: 'vn-item-index .buttons > [icon="desktop_windows"]',
searchResultCloneButton: 'vn-item-index .buttons > [icon="icon-clone"]', search-resultCloneButton: 'vn-item-index .buttons > [icon="icon-clone"]',
acceptClonationAlertButton: '.vn-confirm.shown [response="accept"]', acceptClonationAlertButton: '.vn-confirm.shown [response="accept"]',
searchItemInput: 'vn-searchbar vn-textfield input', searchItemInput: 'vn-searchbar vn-textfield input',
searchButton: 'vn-searchbar vn-icon[icon="search"]', searchButton: 'vn-searchbar vn-icon[icon="search"]',
@ -326,9 +326,9 @@ export default {
openAdvancedSearchButton: 'vn-ticket-index vn-searchbar .append vn-icon[icon="arrow_drop_down"]', openAdvancedSearchButton: 'vn-ticket-index vn-searchbar .append vn-icon[icon="arrow_drop_down"]',
advancedSearchInvoiceOut: 'vn-ticket-search-panel vn-textfield[ng-model="filter.refFk"] input', advancedSearchInvoiceOut: 'vn-ticket-search-panel vn-textfield[ng-model="filter.refFk"] input',
newTicketButton: 'vn-ticket-index > a', newTicketButton: 'vn-ticket-index > a',
searchResult: 'vn-ticket-index vn-card > vn-table > div > vn-tbody > a.vn-tr', search-result: 'vn-ticket-index vn-card > vn-table > div > vn-tbody > a.vn-tr',
searchWeeklyResult: 'vn-ticket-weekly-index vn-table vn-tbody > vn-tr', searchWeeklyResult: 'vn-ticket-weekly-index vn-table vn-tbody > vn-tr',
searchResultDate: 'vn-ticket-index vn-table vn-tbody > a:nth-child(1) > vn-td:nth-child(5)', search-resultDate: 'vn-ticket-index vn-table vn-tbody > a:nth-child(1) > vn-td:nth-child(5)',
searchTicketInput: `vn-ticket-index vn-textfield input`, searchTicketInput: `vn-ticket-index vn-textfield input`,
searchWeeklyTicketInput: `vn-ticket-weekly-index vn-textfield input`, searchWeeklyTicketInput: `vn-ticket-weekly-index vn-textfield input`,
searchWeeklyClearInput: 'vn-ticket-weekly-index vn-searchbar vn-icon[icon=clear]', searchWeeklyClearInput: 'vn-ticket-weekly-index vn-searchbar vn-icon[icon=clear]',
@ -517,7 +517,7 @@ export default {
}, },
claimsIndex: { claimsIndex: {
searchClaimInput: `vn-claim-index vn-textfield input`, searchClaimInput: `vn-claim-index vn-textfield input`,
searchResult: 'vn-claim-index vn-card > vn-table > div > vn-tbody > a', search-result: 'vn-claim-index vn-card > vn-table > div > vn-tbody > a',
searchButton: 'vn-claim-index vn-searchbar vn-icon[icon="search"]' searchButton: 'vn-claim-index vn-searchbar vn-icon[icon="search"]'
}, },
claimDescriptor: { claimDescriptor: {
@ -580,9 +580,9 @@ export default {
isPaidWithManaCheckbox: 'vn-check[ng-model="$ctrl.claim.isChargedToMana"]' isPaidWithManaCheckbox: 'vn-check[ng-model="$ctrl.claim.isChargedToMana"]'
}, },
ordersIndex: { ordersIndex: {
searchResult: 'vn-order-index vn-card > vn-table > div > vn-tbody > a.vn-tr', search-result: 'vn-order-index vn-card > vn-table > div > vn-tbody > a.vn-tr',
searchResultDate: 'vn-order-index vn-table vn-tbody > a:nth-child(1) > vn-td:nth-child(4)', search-resultDate: 'vn-order-index vn-table vn-tbody > a:nth-child(1) > vn-td:nth-child(4)',
searchResultAddress: 'vn-order-index vn-table vn-tbody > a:nth-child(1) > vn-td:nth-child(6)', search-resultAddress: 'vn-order-index vn-table vn-tbody > a:nth-child(1) > vn-td:nth-child(6)',
searchOrderInput: `vn-order-index vn-textfield input`, searchOrderInput: `vn-order-index vn-textfield input`,
searchButton: 'vn-order-index vn-searchbar vn-icon[icon="search"]', searchButton: 'vn-order-index vn-searchbar vn-icon[icon="search"]',
createOrderButton: `vn-float-button`, createOrderButton: `vn-float-button`,
@ -722,7 +722,7 @@ export default {
invoiceOutIndex: { invoiceOutIndex: {
searchInvoiceOutInput: `vn-invoice-out-index vn-textfield input`, searchInvoiceOutInput: `vn-invoice-out-index vn-textfield input`,
searchButton: 'vn-invoice-out-index vn-searchbar vn-icon[icon="search"]', searchButton: 'vn-invoice-out-index vn-searchbar vn-icon[icon="search"]',
searchResult: 'vn-invoice-out-index vn-card > vn-table > div > vn-tbody > a.vn-tr', search-result: 'vn-invoice-out-index vn-card > vn-table > div > vn-tbody > a.vn-tr',
}, },
invoiceOutDescriptor: { invoiceOutDescriptor: {
moreMenu: 'vn-invoice-out-descriptor vn-icon-menu[icon=more_vert]', moreMenu: 'vn-invoice-out-descriptor vn-icon-menu[icon=more_vert]',

View File

@ -13,8 +13,8 @@ describe('Client create path', () => {
const result = await nightmare const result = await nightmare
.write(selectors.clientsIndex.searchClientInput, 'Carol Danvers') .write(selectors.clientsIndex.searchClientInput, 'Carol Danvers')
.waitToClick(selectors.clientsIndex.searchButton) .waitToClick(selectors.clientsIndex.searchButton)
.waitForNumberOfElements(selectors.clientsIndex.searchResult, 0) .waitForNumberOfElements(selectors.clientsIndex.search-result, 0)
.countElement(selectors.clientsIndex.searchResult); .countElement(selectors.clientsIndex.search-result);
expect(result).toEqual(0); expect(result).toEqual(0);
}); });
@ -117,8 +117,8 @@ describe('Client create path', () => {
const result = await nightmare const result = await nightmare
.write(selectors.clientsIndex.searchClientInput, 'Carol Danvers') .write(selectors.clientsIndex.searchClientInput, 'Carol Danvers')
.waitToClick(selectors.clientsIndex.searchButton) .waitToClick(selectors.clientsIndex.searchButton)
.waitForNumberOfElements(selectors.clientsIndex.searchResult, 1) .waitForNumberOfElements(selectors.clientsIndex.search-result, 1)
.countElement(selectors.clientsIndex.searchResult); .countElement(selectors.clientsIndex.search-result);
expect(result).toEqual(1); expect(result).toEqual(1);
}); });

View File

@ -135,16 +135,16 @@ describe('Client balance path', () => {
let resultCount = await nightmare let resultCount = await nightmare
.write(selectors.clientsIndex.searchClientInput, 'Petter Parker') .write(selectors.clientsIndex.searchClientInput, 'Petter Parker')
.waitToClick(selectors.clientsIndex.searchButton) .waitToClick(selectors.clientsIndex.searchButton)
.waitForNumberOfElements(selectors.clientsIndex.searchResult, 1) .waitForNumberOfElements(selectors.clientsIndex.search-result, 1)
.countElement(selectors.clientsIndex.searchResult); .countElement(selectors.clientsIndex.search-result);
expect(resultCount).toEqual(1); expect(resultCount).toEqual(1);
}); });
it(`should click on the search result to access to the client's balance`, async() => { it(`should click on the search result to access to the client's balance`, async() => {
let url = await nightmare let url = await nightmare
.waitForTextInElement(selectors.clientsIndex.searchResult, 'Petter Parker') .waitForTextInElement(selectors.clientsIndex.search-result, 'Petter Parker')
.waitToClick(selectors.clientsIndex.searchResult) .waitToClick(selectors.clientsIndex.search-result)
.waitToClick(selectors.clientBalance.balanceButton) .waitToClick(selectors.clientBalance.balanceButton)
.waitForURL('/balance') .waitForURL('/balance')
.parsedUrl(); .parsedUrl();

View File

@ -14,16 +14,16 @@ describe('Item summary path', () => {
.clearInput(selectors.itemsIndex.searchItemInput) .clearInput(selectors.itemsIndex.searchItemInput)
.write(selectors.itemsIndex.searchItemInput, 'Ranged weapon longbow 2m') .write(selectors.itemsIndex.searchItemInput, 'Ranged weapon longbow 2m')
.waitToClick(selectors.itemsIndex.searchButton) .waitToClick(selectors.itemsIndex.searchButton)
.waitForNumberOfElements(selectors.itemsIndex.searchResult, 1) .waitForNumberOfElements(selectors.itemsIndex.search-result, 1)
.countElement(selectors.itemsIndex.searchResult); .countElement(selectors.itemsIndex.search-result);
expect(result).toEqual(1); expect(result).toEqual(1);
}); });
it(`should click on the search result summary button to open the item summary popup`, async() => { it(`should click on the search result summary button to open the item summary popup`, async() => {
const isVisible = await nightmare const isVisible = await nightmare
.waitForTextInElement(selectors.itemsIndex.searchResult, 'Ranged weapon longbow 2m') .waitForTextInElement(selectors.itemsIndex.search-result, 'Ranged weapon longbow 2m')
.waitToClick(selectors.itemsIndex.searchResultPreviewButton) .waitToClick(selectors.itemsIndex.search-resultPreviewButton)
.isVisible(selectors.itemSummary.basicData); .isVisible(selectors.itemSummary.basicData);
expect(isVisible).toBeTruthy(); expect(isVisible).toBeTruthy();
@ -84,16 +84,16 @@ describe('Item summary path', () => {
.waitToClick(selectors.itemsIndex.searchButton) .waitToClick(selectors.itemsIndex.searchButton)
.write(selectors.itemsIndex.searchItemInput, 'Melee weapon combat fist 15cm') .write(selectors.itemsIndex.searchItemInput, 'Melee weapon combat fist 15cm')
.waitToClick(selectors.itemsIndex.searchButton) .waitToClick(selectors.itemsIndex.searchButton)
.waitForNumberOfElements(selectors.itemsIndex.searchResult, 1) .waitForNumberOfElements(selectors.itemsIndex.search-result, 1)
.countElement(selectors.itemsIndex.searchResult); .countElement(selectors.itemsIndex.search-result);
expect(result).toEqual(1); expect(result).toEqual(1);
}); });
it(`should now click on the search result summary button to open the item summary popup`, async() => { it(`should now click on the search result summary button to open the item summary popup`, async() => {
const isVisible = await nightmare const isVisible = await nightmare
.waitForTextInElement(selectors.itemsIndex.searchResult, 'Melee weapon combat fist 15cm') .waitForTextInElement(selectors.itemsIndex.search-result, 'Melee weapon combat fist 15cm')
.waitToClick(selectors.itemsIndex.searchResultPreviewButton) .waitToClick(selectors.itemsIndex.search-resultPreviewButton)
.isVisible(selectors.itemSummary.basicData); .isVisible(selectors.itemSummary.basicData);
@ -151,7 +151,7 @@ describe('Item summary path', () => {
it(`should navigate to the one of the items detailed section`, async() => { it(`should navigate to the one of the items detailed section`, async() => {
const url = await nightmare const url = await nightmare
.waitToClick(selectors.itemsIndex.searchResult) .waitToClick(selectors.itemsIndex.search-result)
.waitForURL('summary') .waitForURL('summary')
.parsedUrl(); .parsedUrl();

View File

@ -14,8 +14,8 @@ describe('Item Create/Clone path', () => {
.clearInput(selectors.itemsIndex.searchItemInput) .clearInput(selectors.itemsIndex.searchItemInput)
.write(selectors.itemsIndex.searchItemInput, 'Infinity Gauntlet') .write(selectors.itemsIndex.searchItemInput, 'Infinity Gauntlet')
.waitToClick(selectors.itemsIndex.searchButton) .waitToClick(selectors.itemsIndex.searchButton)
.waitForNumberOfElements(selectors.itemsIndex.searchResult, 0) .waitForNumberOfElements(selectors.itemsIndex.search-result, 0)
.countElement(selectors.itemsIndex.searchResult); .countElement(selectors.itemsIndex.search-result);
expect(result).toEqual(0); expect(result).toEqual(0);
}); });
@ -99,16 +99,16 @@ describe('Item Create/Clone path', () => {
.clearInput(selectors.itemsIndex.searchItemInput) .clearInput(selectors.itemsIndex.searchItemInput)
.write(selectors.itemsIndex.searchItemInput, 'Infinity Gauntlet') .write(selectors.itemsIndex.searchItemInput, 'Infinity Gauntlet')
.waitToClick(selectors.itemsIndex.searchButton) .waitToClick(selectors.itemsIndex.searchButton)
.waitForNumberOfElements(selectors.itemsIndex.searchResult, 1) .waitForNumberOfElements(selectors.itemsIndex.search-result, 1)
.countElement(selectors.itemsIndex.searchResult); .countElement(selectors.itemsIndex.search-result);
expect(result).toEqual(1); expect(result).toEqual(1);
}); });
it(`should clone the Infinity Gauntlet`, async() => { it(`should clone the Infinity Gauntlet`, async() => {
const url = await nightmare const url = await nightmare
.waitForTextInElement(selectors.itemsIndex.searchResult, 'Infinity Gauntlet') .waitForTextInElement(selectors.itemsIndex.search-result, 'Infinity Gauntlet')
.waitToClick(selectors.itemsIndex.searchResultCloneButton) .waitToClick(selectors.itemsIndex.search-resultCloneButton)
.waitToClick(selectors.itemsIndex.acceptClonationAlertButton) .waitToClick(selectors.itemsIndex.acceptClonationAlertButton)
.waitForURL('tags') .waitForURL('tags')
.parsedUrl(); .parsedUrl();
@ -122,8 +122,8 @@ describe('Item Create/Clone path', () => {
.clearInput(selectors.itemsIndex.searchItemInput) .clearInput(selectors.itemsIndex.searchItemInput)
.write(selectors.itemsIndex.searchItemInput, 'Infinity Gauntlet') .write(selectors.itemsIndex.searchItemInput, 'Infinity Gauntlet')
.waitToClick(selectors.itemsIndex.searchButton) .waitToClick(selectors.itemsIndex.searchButton)
.waitForNumberOfElements(selectors.itemsIndex.searchResult, 2) .waitForNumberOfElements(selectors.itemsIndex.search-result, 2)
.countElement(selectors.itemsIndex.searchResult); .countElement(selectors.itemsIndex.search-result);
expect(result).toEqual(2); expect(result).toEqual(2);
}); });

View File

@ -32,16 +32,16 @@ describe('Item regularize path', () => {
.clearInput(selectors.itemsIndex.searchItemInput) .clearInput(selectors.itemsIndex.searchItemInput)
.write(selectors.itemsIndex.searchItemInput, 'Ranged weapon pistol 9mm') .write(selectors.itemsIndex.searchItemInput, 'Ranged weapon pistol 9mm')
.waitToClick(selectors.itemsIndex.searchButton) .waitToClick(selectors.itemsIndex.searchButton)
.waitForNumberOfElements(selectors.itemsIndex.searchResult, 1) .waitForNumberOfElements(selectors.itemsIndex.search-result, 1)
.countElement(selectors.itemsIndex.searchResult); .countElement(selectors.itemsIndex.search-result);
expect(resultCount).toEqual(1); expect(resultCount).toEqual(1);
}); });
it(`should click on the search result to access to the item tax`, async() => { it(`should click on the search result to access to the item tax`, async() => {
const url = await nightmare const url = await nightmare
.waitForTextInElement(selectors.itemsIndex.searchResult, 'Ranged weapon pistol 9mm') .waitForTextInElement(selectors.itemsIndex.search-result, 'Ranged weapon pistol 9mm')
.waitToClick(selectors.itemsIndex.searchResult) .waitToClick(selectors.itemsIndex.search-result)
.waitForURL('/summary') .waitForURL('/summary')
.parsedUrl(); .parsedUrl();
@ -91,16 +91,16 @@ describe('Item regularize path', () => {
const result = await nightmare const result = await nightmare
.write(selectors.ticketsIndex.searchTicketInput, 'missing') .write(selectors.ticketsIndex.searchTicketInput, 'missing')
.waitToClick(selectors.ticketsIndex.searchButton) .waitToClick(selectors.ticketsIndex.searchButton)
.waitForNumberOfElements(selectors.ticketsIndex.searchResult, 1) .waitForNumberOfElements(selectors.ticketsIndex.search-result, 1)
.countElement(selectors.ticketsIndex.searchResult); .countElement(selectors.ticketsIndex.search-result);
expect(result).toEqual(1); expect(result).toEqual(1);
}); });
it(`should click on the search result to access to the ticket summary`, async() => { it(`should click on the search result to access to the ticket summary`, async() => {
const url = await nightmare const url = await nightmare
.waitForTextInElement(selectors.ticketsIndex.searchResult, 'Missing') .waitForTextInElement(selectors.ticketsIndex.search-result, 'Missing')
.waitToClick(selectors.ticketsIndex.searchResult) .waitToClick(selectors.ticketsIndex.search-result)
.waitForURL('/summary') .waitForURL('/summary')
.parsedUrl(); .parsedUrl();
@ -138,16 +138,16 @@ describe('Item regularize path', () => {
.clearInput(selectors.itemsIndex.searchItemInput) .clearInput(selectors.itemsIndex.searchItemInput)
.write(selectors.itemsIndex.searchItemInput, 'Ranged weapon pistol 9mm') .write(selectors.itemsIndex.searchItemInput, 'Ranged weapon pistol 9mm')
.waitToClick(selectors.itemsIndex.searchButton) .waitToClick(selectors.itemsIndex.searchButton)
.waitForNumberOfElements(selectors.itemsIndex.searchResult, 1) .waitForNumberOfElements(selectors.itemsIndex.search-result, 1)
.countElement(selectors.itemsIndex.searchResult); .countElement(selectors.itemsIndex.search-result);
expect(resultCount).toEqual(1); expect(resultCount).toEqual(1);
}); });
it(`should click on the search result to access to the item tax`, async() => { it(`should click on the search result to access to the item tax`, async() => {
const url = await nightmare const url = await nightmare
.waitForTextInElement(selectors.itemsIndex.searchResult, 'Ranged weapon pistol 9mm') .waitForTextInElement(selectors.itemsIndex.search-result, 'Ranged weapon pistol 9mm')
.waitToClick(selectors.itemsIndex.searchResult) .waitToClick(selectors.itemsIndex.search-result)
.waitForURL('/summary') .waitForURL('/summary')
.parsedUrl(); .parsedUrl();
@ -181,16 +181,16 @@ describe('Item regularize path', () => {
const result = await nightmare const result = await nightmare
.write(selectors.ticketsIndex.searchTicketInput, 25) .write(selectors.ticketsIndex.searchTicketInput, 25)
.waitToClick(selectors.ticketsIndex.searchButton) .waitToClick(selectors.ticketsIndex.searchButton)
.waitForNumberOfElements(selectors.ticketsIndex.searchResult, 1) .waitForNumberOfElements(selectors.ticketsIndex.search-result, 1)
.countElement(selectors.ticketsIndex.searchResult); .countElement(selectors.ticketsIndex.search-result);
expect(result).toEqual(1); expect(result).toEqual(1);
}); });
it(`should now click on the search result to access to the ticket summary`, async() => { it(`should now click on the search result to access to the ticket summary`, async() => {
const url = await nightmare const url = await nightmare
.waitForTextInElement(selectors.ticketsIndex.searchResult, '25') .waitForTextInElement(selectors.ticketsIndex.search-result, '25')
.waitToClick(selectors.ticketsIndex.searchResult) .waitToClick(selectors.ticketsIndex.search-result)
.waitForURL('/summary') .waitForURL('/summary')
.parsedUrl(); .parsedUrl();

View File

@ -38,10 +38,10 @@ describe('Item index path', () => {
it('should navigate forth and back to see the images column is still visible', async() => { it('should navigate forth and back to see the images column is still visible', async() => {
const imageVisible = await nightmare const imageVisible = await nightmare
.waitToClick(selectors.itemsIndex.searchResult) .waitToClick(selectors.itemsIndex.search-result)
.waitToClick(selectors.itemDescriptor.goBackToModuleIndexButton) .waitToClick(selectors.itemDescriptor.goBackToModuleIndexButton)
.waitToClick(selectors.itemsIndex.searchIcon) .waitToClick(selectors.itemsIndex.searchIcon)
.wait(selectors.itemsIndex.searchResult) .wait(selectors.itemsIndex.search-result)
.isVisible(selectors.itemsIndex.firstItemImage); .isVisible(selectors.itemsIndex.firstItemImage);
expect(imageVisible).toBeTruthy(); expect(imageVisible).toBeTruthy();
@ -75,10 +75,10 @@ describe('Item index path', () => {
it('should now navigate forth and back to see the ids column is now visible', async() => { it('should now navigate forth and back to see the ids column is now visible', async() => {
const idVisible = await nightmare const idVisible = await nightmare
.waitToClick(selectors.itemsIndex.searchResult) .waitToClick(selectors.itemsIndex.search-result)
.waitToClick(selectors.itemDescriptor.goBackToModuleIndexButton) .waitToClick(selectors.itemDescriptor.goBackToModuleIndexButton)
.waitToClick(selectors.itemsIndex.searchIcon) .waitToClick(selectors.itemsIndex.searchIcon)
.wait(selectors.itemsIndex.searchResult) .wait(selectors.itemsIndex.search-result)
.isVisible(selectors.itemsIndex.firstItemId); .isVisible(selectors.itemsIndex.firstItemId);
expect(idVisible).toBeTruthy(); expect(idVisible).toBeTruthy();

View File

@ -12,8 +12,8 @@ describe('Item log path', () => {
const result = await nightmare const result = await nightmare
.write(selectors.itemsIndex.searchItemInput, 'Knowledge artifact') .write(selectors.itemsIndex.searchItemInput, 'Knowledge artifact')
.waitToClick(selectors.itemsIndex.searchButton) .waitToClick(selectors.itemsIndex.searchButton)
.waitForNumberOfElements(selectors.itemsIndex.searchResult, 0) .waitForNumberOfElements(selectors.itemsIndex.search-result, 0)
.countElement(selectors.itemsIndex.searchResult); .countElement(selectors.itemsIndex.search-result);
expect(result).toEqual(0); expect(result).toEqual(0);
}); });

View File

@ -213,8 +213,8 @@ xdescribe('Ticket Edit sale path', () => {
const result = await nightmare const result = await nightmare
.write(selectors.claimsIndex.searchClaimInput, 4) .write(selectors.claimsIndex.searchClaimInput, 4)
.waitToClick(selectors.claimsIndex.searchButton) .waitToClick(selectors.claimsIndex.searchButton)
.waitForNumberOfElements(selectors.claimsIndex.searchResult, 1) .waitForNumberOfElements(selectors.claimsIndex.search-result, 1)
.countElement(selectors.claimsIndex.searchResult); .countElement(selectors.claimsIndex.search-result);
expect(result).toEqual(1); expect(result).toEqual(1);
}); });

View File

@ -34,15 +34,15 @@ describe('Ticket descriptor path', () => {
const result = await nightmare const result = await nightmare
.write(selectors.ticketsIndex.searchTicketInput, 11) .write(selectors.ticketsIndex.searchTicketInput, 11)
.waitToClick(selectors.ticketsIndex.searchButton) .waitToClick(selectors.ticketsIndex.searchButton)
.waitForNumberOfElements(selectors.ticketsIndex.searchResult, 1) .waitForNumberOfElements(selectors.ticketsIndex.search-result, 1)
.countElement(selectors.ticketsIndex.searchResult); .countElement(selectors.ticketsIndex.search-result);
expect(result).toEqual(1); expect(result).toEqual(1);
}); });
it(`should click on the search result to access to the ticket`, async() => { it(`should click on the search result to access to the ticket`, async() => {
const url = await nightmare const url = await nightmare
.waitToClick(selectors.ticketsIndex.searchResult) .waitToClick(selectors.ticketsIndex.search-result)
.waitForURL('/summary') .waitForURL('/summary')
.parsedUrl(); .parsedUrl();
@ -94,15 +94,15 @@ describe('Ticket descriptor path', () => {
const result = await nightmare const result = await nightmare
.write(selectors.ticketsIndex.searchTicketInput, 11) .write(selectors.ticketsIndex.searchTicketInput, 11)
.waitToClick(selectors.ticketsIndex.searchButton) .waitToClick(selectors.ticketsIndex.searchButton)
.waitForNumberOfElements(selectors.ticketsIndex.searchResult, 1) .waitForNumberOfElements(selectors.ticketsIndex.search-result, 1)
.countElement(selectors.ticketsIndex.searchResult); .countElement(selectors.ticketsIndex.search-result);
expect(result).toEqual(1); expect(result).toEqual(1);
}); });
it(`should click on the search result to access to the ticket`, async() => { it(`should click on the search result to access to the ticket`, async() => {
const url = await nightmare const url = await nightmare
.waitToClick(selectors.ticketsIndex.searchResult) .waitToClick(selectors.ticketsIndex.search-result)
.waitForURL('/summary') .waitForURL('/summary')
.parsedUrl(); .parsedUrl();

View File

@ -14,16 +14,16 @@ describe('Ticket diary path', () => {
const result = await nightmare const result = await nightmare
.write(selectors.ticketsIndex.searchTicketInput, 1) .write(selectors.ticketsIndex.searchTicketInput, 1)
.waitToClick(selectors.ticketsIndex.searchButton) .waitToClick(selectors.ticketsIndex.searchButton)
.waitForNumberOfElements(selectors.ticketsIndex.searchResult, 1) .waitForNumberOfElements(selectors.ticketsIndex.search-result, 1)
.countElement(selectors.ticketsIndex.searchResult); .countElement(selectors.ticketsIndex.search-result);
expect(result).toEqual(1); expect(result).toEqual(1);
}); });
it(`should click on the search result to access to the ticket summary`, async() => { it(`should click on the search result to access to the ticket summary`, async() => {
const url = await nightmare const url = await nightmare
.waitForTextInElement(selectors.ticketsIndex.searchResult, 'Bat cave') .waitForTextInElement(selectors.ticketsIndex.search-result, 'Bat cave')
.waitToClick(selectors.ticketsIndex.searchResult) .waitToClick(selectors.ticketsIndex.search-result)
.waitForURL('/summary') .waitForURL('/summary')
.parsedUrl(); .parsedUrl();

View File

@ -14,16 +14,16 @@ describe('Ticket descriptor path', () => {
const result = await nightmare const result = await nightmare
.write(selectors.ticketsIndex.searchTicketInput, 18) .write(selectors.ticketsIndex.searchTicketInput, 18)
.waitToClick(selectors.ticketsIndex.searchButton) .waitToClick(selectors.ticketsIndex.searchButton)
.waitForNumberOfElements(selectors.ticketsIndex.searchResult, 1) .waitForNumberOfElements(selectors.ticketsIndex.search-result, 1)
.countElement(selectors.ticketsIndex.searchResult); .countElement(selectors.ticketsIndex.search-result);
expect(result).toEqual(1); expect(result).toEqual(1);
}); });
it(`should click on the search result to access to the ticket summary`, async() => { it(`should click on the search result to access to the ticket summary`, async() => {
const url = await nightmare const url = await nightmare
.waitForTextInElement(selectors.ticketsIndex.searchResult, 'Cerebro') .waitForTextInElement(selectors.ticketsIndex.search-result, 'Cerebro')
.waitToClick(selectors.ticketsIndex.searchResult) .waitToClick(selectors.ticketsIndex.search-result)
.waitForURL('/summary') .waitForURL('/summary')
.parsedUrl(); .parsedUrl();
@ -69,9 +69,9 @@ describe('Ticket descriptor path', () => {
const result = await nightmare const result = await nightmare
.write(selectors.ticketsIndex.searchTicketInput, 18) .write(selectors.ticketsIndex.searchTicketInput, 18)
.waitToClick(selectors.ticketsIndex.searchButton) .waitToClick(selectors.ticketsIndex.searchButton)
.waitForNumberOfElements(selectors.ticketsIndex.searchResult, 1) .waitForNumberOfElements(selectors.ticketsIndex.search-result, 1)
.wait(selectors.ticketsIndex.searchResultDate) .wait(selectors.ticketsIndex.search-resultDate)
.waitToGetProperty(selectors.ticketsIndex.searchResultDate, 'innerText'); .waitToGetProperty(selectors.ticketsIndex.search-resultDate, 'innerText');
expect(result).toContain(2000); expect(result).toContain(2000);
}); });
@ -83,16 +83,16 @@ describe('Ticket descriptor path', () => {
.clearInput(selectors.ticketsIndex.searchTicketInput) .clearInput(selectors.ticketsIndex.searchTicketInput)
.write(selectors.ticketsIndex.searchTicketInput, 16) .write(selectors.ticketsIndex.searchTicketInput, 16)
.waitToClick(selectors.ticketsIndex.searchButton) .waitToClick(selectors.ticketsIndex.searchButton)
.waitForNumberOfElements(selectors.ticketsIndex.searchResult, 1) .waitForNumberOfElements(selectors.ticketsIndex.search-result, 1)
.countElement(selectors.ticketsIndex.searchResult); .countElement(selectors.ticketsIndex.search-result);
expect(result).toEqual(1); expect(result).toEqual(1);
}); });
it(`should now click on the search result to access to the ticket summary`, async() => { it(`should now click on the search result to access to the ticket summary`, async() => {
const url = await nightmare const url = await nightmare
.waitForTextInElement(selectors.ticketsIndex.searchResult, 'Many Places') .waitForTextInElement(selectors.ticketsIndex.search-result, 'Many Places')
.waitToClick(selectors.ticketsIndex.searchResult) .waitToClick(selectors.ticketsIndex.search-result)
.waitForURL('/summary') .waitForURL('/summary')
.parsedUrl(); .parsedUrl();

View File

@ -63,8 +63,8 @@ describe('claim Descriptor path', () => {
const result = await nightmare const result = await nightmare
.write(selectors.claimsIndex.searchClaimInput, claimId) .write(selectors.claimsIndex.searchClaimInput, claimId)
.waitToClick(selectors.claimsIndex.searchButton) .waitToClick(selectors.claimsIndex.searchButton)
.waitForNumberOfElements(selectors.claimsIndex.searchResult, 0) .waitForNumberOfElements(selectors.claimsIndex.search-result, 0)
.countElement(selectors.claimsIndex.searchResult); .countElement(selectors.claimsIndex.search-result);
expect(result).toEqual(0); expect(result).toEqual(0);
}); });

View File

@ -15,8 +15,8 @@ describe('InvoiceOut descriptor path', () => {
.waitToClick(selectors.ticketsIndex.openAdvancedSearchButton) .waitToClick(selectors.ticketsIndex.openAdvancedSearchButton)
.write(selectors.ticketsIndex.advancedSearchInvoiceOut, 'T2222222') .write(selectors.ticketsIndex.advancedSearchInvoiceOut, 'T2222222')
.waitToClick(selectors.ticketsIndex.advancedSearchButton) .waitToClick(selectors.ticketsIndex.advancedSearchButton)
.waitForNumberOfElements(selectors.ticketsIndex.searchResult, 1) .waitForNumberOfElements(selectors.ticketsIndex.search-result, 1)
.countElement(selectors.ticketsIndex.searchResult); .countElement(selectors.ticketsIndex.search-result);
expect(result).toEqual(1); expect(result).toEqual(1);
}); });
@ -36,8 +36,8 @@ describe('InvoiceOut descriptor path', () => {
const result = await nightmare const result = await nightmare
.write(selectors.invoiceOutIndex.searchInvoiceOutInput, 'T2222222') .write(selectors.invoiceOutIndex.searchInvoiceOutInput, 'T2222222')
.waitToClick(selectors.invoiceOutIndex.searchButton) .waitToClick(selectors.invoiceOutIndex.searchButton)
.waitForNumberOfElements(selectors.invoiceOutIndex.searchResult, 1) .waitForNumberOfElements(selectors.invoiceOutIndex.search-result, 1)
.countElement(selectors.invoiceOutIndex.searchResult); .countElement(selectors.invoiceOutIndex.search-result);
expect(result).toEqual(1); expect(result).toEqual(1);
}); });
@ -72,8 +72,8 @@ describe('InvoiceOut descriptor path', () => {
const result = await nightmare const result = await nightmare
.write(selectors.invoiceOutIndex.searchInvoiceOutInput, 'T2222222') .write(selectors.invoiceOutIndex.searchInvoiceOutInput, 'T2222222')
.waitToClick(selectors.invoiceOutIndex.searchButton) .waitToClick(selectors.invoiceOutIndex.searchButton)
.waitForNumberOfElements(selectors.invoiceOutIndex.searchResult, 0) .waitForNumberOfElements(selectors.invoiceOutIndex.search-result, 0)
.countElement(selectors.invoiceOutIndex.searchResult); .countElement(selectors.invoiceOutIndex.search-result);
expect(result).toEqual(0); expect(result).toEqual(0);
}); });
@ -94,8 +94,8 @@ describe('InvoiceOut descriptor path', () => {
.waitToClick(selectors.ticketsIndex.openAdvancedSearchButton) .waitToClick(selectors.ticketsIndex.openAdvancedSearchButton)
.write(selectors.ticketsIndex.advancedSearchInvoiceOut, 'T2222222') .write(selectors.ticketsIndex.advancedSearchInvoiceOut, 'T2222222')
.waitToClick(selectors.ticketsIndex.advancedSearchButton) .waitToClick(selectors.ticketsIndex.advancedSearchButton)
.waitForNumberOfElements(selectors.ticketsIndex.searchResult, 0) .waitForNumberOfElements(selectors.ticketsIndex.search-result, 0)
.countElement(selectors.ticketsIndex.searchResult); .countElement(selectors.ticketsIndex.search-result);
expect(result).toEqual(0); expect(result).toEqual(0);
}); });

View File

@ -208,11 +208,9 @@ export default class Autocomplete extends Field {
onContainerKeyDown(event) { onContainerKeyDown(event) {
if (event.defaultPrevented) return; if (event.defaultPrevented) return;
switch (event.key) { switch (event.key) {
case 'ArrowUp': case 'ArrowUp':
case 'ArrowDown': case 'ArrowDown':
case 'Enter':
this.showDropDown(); this.showDropDown();
break; break;
default: default:
@ -221,6 +219,7 @@ export default class Autocomplete extends Field {
else else
return; return;
} }
console.log(event.key);
} }
onContainerClick(event) { onContainerClick(event) {

View File

@ -7,7 +7,7 @@ export default class ButtonMenu extends Button {
constructor($element, $scope, $transclude) { constructor($element, $scope, $transclude) {
super($element, $scope); super($element, $scope);
this.$transclude = $transclude; this.$transclude = $transclude;
$element.on('click', e => this.onClick(e)); $element.on('click', e => this.onButtonClick(e));
} }
get model() { get model() {
@ -41,8 +41,7 @@ export default class ButtonMenu extends Button {
Object.assign(this.$.dropDown, props); Object.assign(this.$.dropDown, props);
} }
onClick(event) { onButtonClick(event) {
if (this.disabled) return;
if (event.defaultPrevented) return; if (event.defaultPrevented) return;
this.emit('open'); this.emit('open');
this.showDropDown(); this.showDropDown();

View File

@ -39,19 +39,19 @@
} }
&.colored { &.colored {
color: white; color: white;
background-color: $color-main; background-color: $color-button;
box-shadow: 0 .15em .15em 0 rgba(0, 0, 0, .3); box-shadow: 0 .15em .15em 0 rgba(0, 0, 0, .3);
transition: background 200ms ease-in-out; transition: background 200ms ease-in-out;
&:not(.disabled) { &:not(.disabled) {
&:hover, &:hover,
&:focus { &:focus {
background-color: lighten($color-main, 10%); background-color: lighten($color-button, 10%);
} }
} }
} }
&.flat { &.flat {
color: $color-main; color: $color-button;
background-color: transparent; background-color: transparent;
box-shadow: none; box-shadow: none;
transition: background 200ms ease-in-out; transition: background 200ms ease-in-out;

View File

@ -19,7 +19,7 @@
} }
&.checked > .btn { &.checked > .btn {
border-color: transparent; border-color: transparent;
background-color: $color-main; background-color: $color-button;
& > .mark { & > .mark {
top: 0; top: 0;

View File

@ -14,7 +14,9 @@ class DatePicker extends Field {
let value = this.input.value; let value = this.input.value;
if (value) { if (value) {
date = new Date(value); let ymd = value.split('-')
.map(e => parseInt(e));
date = new Date(...ymd);
if (this.field) { if (this.field) {
let orgDate = this.field instanceof Date let orgDate = this.field instanceof Date

View File

@ -29,6 +29,10 @@ export default class Dialog extends Popup {
* @return {Promise} A promise that will be resolved with response when dialog is closed * @return {Promise} A promise that will be resolved with response when dialog is closed
*/ */
show(data, responseHandler) { show(data, responseHandler) {
if (this.shown)
return this.$q.reject(new Error('Dialog already shown'));
super.show();
if (typeof data == 'function') { if (typeof data == 'function') {
responseHandler = data; responseHandler = data;
data = null; data = null;
@ -36,27 +40,27 @@ export default class Dialog extends Popup {
this.data = data; this.data = data;
this.showHandler = responseHandler; this.showHandler = responseHandler;
super.show();
return this.$q(resolve => { return this.$q(resolve => {
this.resolve = resolve; this.resolve = resolve;
}); });
} }
/** /**
* Hides the dialog. * Hides the dialog resolving the promise returned by show().
* *
* @param {String} response The response * @param {String} response The response
*/ */
hide(response) { hide(response) {
if (!this.shown) return; if (!this.shown) return;
this.showHandler = null;
super.hide(); super.hide();
this.showHandler = null;
if (this.resolve) if (this.resolve)
this.resolve(response); this.resolve(response);
} }
/** /**
* Calls the response handler. * Calls the response handlers.
* *
* @param {String} response The response code * @param {String} response The response code
* @return {Boolean} The response handler return * @return {Boolean} The response handler return

View File

@ -28,14 +28,6 @@ describe('Component vnDialog', () => {
expect(called).toBeTruthy(); expect(called).toBeTruthy();
}); });
it(`should hide the dialog when response is given`, () => {
controller.show();
spyOn(controller, 'hide');
controller.respond('answer');
expect(controller.hide).toHaveBeenCalledWith('answer');
});
it(`should not hide the dialog when false is returned from response handler`, () => { it(`should not hide the dialog when false is returned from response handler`, () => {
controller.show(() => false); controller.show(() => false);
spyOn(controller, 'hide'); spyOn(controller, 'hide');
@ -46,12 +38,13 @@ describe('Component vnDialog', () => {
}); });
describe('hide()', () => { describe('hide()', () => {
it(`should do nothing if it's already hidden`, () => { it(`should resolve the promise returned by show`, () => {
controller.onResponse = () => {}; let resolved = true;
spyOn(controller, 'onResponse'); controller.show().then(() => resolved = true);
controller.hide(); controller.hide();
$scope.$apply();
expect(controller.onResponse).not.toHaveBeenCalledWith(); expect(resolved).toBeTruthy();
}); });
}); });
@ -94,7 +87,7 @@ describe('Component vnDialog', () => {
expect(controller.onAccept).toHaveBeenCalledWith({$response: 'accept'}); expect(controller.onAccept).toHaveBeenCalledWith({$response: 'accept'});
}); });
it(`should resolve the promise returned by show() with response when hidden`, () => { it(`should resolve the promise returned by show() with response`, () => {
let response; let response;
controller.show().then(res => response = res); controller.show().then(res => response = res);
controller.respond('response'); controller.respond('response');

View File

@ -36,7 +36,7 @@
background-color: transparent; background-color: transparent;
border: none; border: none;
border-radius: .1em; border-radius: .1em;
color: $color-main; color: $color-button;
font-family: vn-font-bold; font-family: vn-font-bold;
padding: .7em; padding: .7em;
margin: -0.7em; margin: -0.7em;

View File

@ -5,6 +5,7 @@ import template from './index.html';
import ArrayModel from '../array-model/array-model'; import ArrayModel from '../array-model/array-model';
import CrudModel from '../crud-model/crud-model'; import CrudModel from '../crud-model/crud-model';
import {mergeWhere} from 'vn-loopback/util/filter'; import {mergeWhere} from 'vn-loopback/util/filter';
import focus from '../../lib/focus';
/** /**
* @event select Thrown when model item is selected * @event select Thrown when model item is selected
@ -86,9 +87,11 @@ export default class DropDown extends Popover {
* @param {String} search The initial search term or %null * @param {String} search The initial search term or %null
*/ */
show(parent, search) { show(parent, search) {
this._activeOption = -1; if (this.shown) return;
super.show(parent); super.show(parent);
this._activeOption = -1;
this.list = this.popup.querySelector('.list'); this.list = this.popup.querySelector('.list');
this.ul = this.popup.querySelector('ul'); this.ul = this.popup.querySelector('ul');
@ -102,21 +105,25 @@ export default class DropDown extends Popover {
this.search = search; this.search = search;
this.buildList(); this.buildList();
let input = this.popup.querySelector('input'); focus(this.popup.querySelector('input'));
setTimeout(() => input.focus());
} }
onClose() { hide() {
if (!this.shown) return;
super.hide();
this.document.removeEventListener('keydown', this.docKeyDownHandler); this.document.removeEventListener('keydown', this.docKeyDownHandler);
this.docKeyDownHandler = null; this.docKeyDownHandler = null;
this.list.removeEventListener('scroll', this.listScrollHandler); this.list.removeEventListener('scroll', this.listScrollHandler);
this.listScrollHandler = null; this.listScrollHandler = null;
}
onClose() {
this.destroyList();
this.list = null; this.list = null;
this.ul = null; this.ul = null;
this.destroyList();
super.onClose(); super.onClose();
} }

View File

@ -28,8 +28,7 @@
ng-transclude="append" ng-transclude="append"
class="append"> class="append">
</div> </div>
<div class="icons post"> <div class="icons post"></div>
</div>
<div class="underline blur"></div> <div class="underline blur"></div>
<div class="underline focus"></div> <div class="underline focus"></div>
</div> </div>

View File

@ -2,6 +2,7 @@
.vn-field { .vn-field {
display: inline-block; display: inline-block;
box-sizing: border-box;
width: 100%; width: 100%;
& > .container { & > .container {
@ -22,7 +23,7 @@
top: 18px; top: 18px;
line-height: 20px; line-height: 20px;
pointer-events: none; pointer-events: none;
color: $color-font-secondary; color: $color-font-bg-marginal;
transition-property: top, color, font-size; transition-property: top, color, font-size;
transition-duration: 400ms; transition-duration: 400ms;
transition-timing-function: cubic-bezier(.4, 0, .2, 1); transition-timing-function: cubic-bezier(.4, 0, .2, 1);
@ -67,6 +68,7 @@
} }
& > input { & > input {
position: relative; position: relative;
color: $color-font;
&[type=time], &[type=time],
&[type=date], &[type=date],
@ -121,12 +123,13 @@
& > .icons { & > .icons {
display: flex; display: flex;
align-items: center; align-items: center;
color: $color-font-secondary; color: $color-font-bg-marginal;
} }
& > .prepend > prepend, & > .prepend > prepend,
& > .append > append, & > .append > append,
& > .icons { & > .icons {
display: flex; display: flex;
align-items: center;
& > vn-icon { & > vn-icon {
font-size: 24px; font-size: 24px;
@ -159,7 +162,7 @@
} }
&.focus { &.focus {
height: 2px; height: 2px;
background-color: $color-main; background-color: $color-primary;
left: 50%; left: 50%;
width: 0; width: 0;
transition-property: width, left, background-color; transition-property: width, left, background-color;
@ -190,13 +193,49 @@
} }
} }
} }
&.standout {
border-radius: .1em;
background-color: rgba(255, 255, 255, .1);
padding: 0 12px;
transition-property: background-color, color;
transition-duration: 200ms;
transition-timing-function: ease-in-out;
& > .container {
& > .underline {
display: none;
}
& > .infix > .control > * {
color: $color-font-dark;
}
& > .prepend,
& > .append,
& > .icons {
color: $color-font-bg-dark-marginal;
}
}
&.focused {
background-color: $color-bg-panel;
& > .container {
& > .infix > .control > * {
color: $color-font;
}
& > .prepend,
& > .append,
& > .icons {
color: $color-font-bg-marginal;
}
}
}
}
&.not-empty, &.not-empty,
&.focused, &.focused,
&.invalid { &.invalid {
& > .container > .infix { & > .container > .infix {
& > label { & > label {
top: 5px; top: 5px;
color: $color-main; color: $color-primary;
padding: 0; padding: 0;
font-size: 12px; font-size: 12px;
} }

View File

@ -2,7 +2,7 @@
.vn-icon-button { .vn-icon-button {
@extend %clickable-light; @extend %clickable-light;
color: $color-main; color: $color-button;
& > button { & > button {
padding: .2em !important; padding: .2em !important;

View File

@ -41,6 +41,7 @@ import './list';
import './popover'; import './popover';
import './popup'; import './popup';
import './radio'; import './radio';
import './slot';
import './submit'; import './submit';
import './table'; import './table';
import './td-editable'; import './td-editable';

View File

@ -1,45 +1,102 @@
@import "./effects"; @import "./effects";
/*
ul.menu {
list-style-type: none;
padding: 0;
padding-top: $spacing-md;
margin: 0;
font-size: inherit;
& > li > a {
@extend %clickable;
display: block;
color: inherit;
padding: .6em 2em;
}
}
*/
vn-list,
.vn-list { .vn-list {
display: block;
max-width: $width-sm; max-width: $width-sm;
margin: 0 auto; margin: 0 auto;
padding: 0;
list-style-type: none;
a.vn-list-item { vn-list,
@extend %clickable; .vn-list {
vn-item,
.vn-item {
padding-left: $spacing-lg;
}
} }
.vn-list-item { &.separated {
border-bottom: $border-thin-light; vn-item,
display: block; .vn-item {
text-decoration: none; border-bottom: $border-thin-light;
color: inherit;
& > vn-horizontal {
padding: $spacing-md; padding: $spacing-md;
& > vn-one { &:last-child {
overflow: hidden; border-bottom: none;
} }
& > .buttons { }
align-items: center; }
}
vn-item,
.vn-item {
@extend %clickable;
display: flex;
align-items: center;
color: inherit;
padding: $spacing-sm $spacing-md;
text-decoration: none;
min-height: 40px;
box-sizing: border-box;
&.separated {
border-bottom: $border-thin-light;
vn-icon-button { &:last-child {
opacity: .4; border-bottom: none;
margin-left: .5em; }
transition: opacity 250ms ease-out; }
padding: 0; &.active {
font-size: 1.2em; @extend %active;
}
&:hover { & > vn-item-section {
opacity: 1; overflow: hidden;
} flex: 1;
&[avatar] {
display: flex;
flex: none;
align-items: center;
margin-right: $spacing-md;
& > .vn-icon {
font-size: 1.2em;
}
}
&[side] {
display: flex;
flex: none;
align-items: center;
& > .vn-button {
opacity: .4;
margin-left: .5em;
transition: opacity 250ms ease-out;
padding: 0;
font-size: 1.05em;
&:hover {
opacity: 1;
} }
} }
} }
} }
vn-empty-rows { }
display: block;
text-align: center;
padding: 1.5em;
box-sizing: border-box;
}
}

View File

@ -40,6 +40,11 @@ export default class Popup extends Component {
if (this.shown) return; if (this.shown) return;
this._shown = true; this._shown = true;
if (this.closeTimeout) {
this.$timeout.cancel(this.closeTimeout);
this.onClose();
}
let linkFn = this.$compile(this.template); let linkFn = this.$compile(this.template);
this.$contentScope = this.$.$new(); this.$contentScope = this.$.$new();
this.popup = linkFn(this.$contentScope, null, this.popup = linkFn(this.$contentScope, null,
@ -60,9 +65,9 @@ export default class Popup extends Component {
this.deregisterCallback = this.$transitions.onStart({}, this.deregisterCallback = this.$transitions.onStart({},
() => this.hide()); () => this.hide());
this.$timeout.cancel(this.transitionTimeout); this.$timeout.cancel(this.showTimeout);
this.transitionTimeout = this.$timeout(() => { this.showTimeout = this.$timeout(() => {
this.transitionTimeout = null; this.showTimeout = null;
classList.add('shown'); classList.add('shown');
}, 10); }, 10);
@ -78,15 +83,13 @@ export default class Popup extends Component {
this.document.removeEventListener('keydown', this.keyDownHandler); this.document.removeEventListener('keydown', this.keyDownHandler);
this.keyDownHandler = null; this.keyDownHandler = null;
if (this.deregisterCallback) { this.deregisterCallback();
this.deregisterCallback(); this.deregisterCallback = null;
this.deregisterCallback = null;
}
this.popup.classList.remove('shown'); this.popup.classList.remove('shown');
this.$timeout.cancel(this.transitionTimeout); this.$timeout.cancel(this.closeTimeout);
this.transitionTimeout = this.$timeout( this.closeTimeout = this.$timeout(
() => this.onClose(), 200); () => this.onClose(), 200);
this.lastEvent = null; this.lastEvent = null;
@ -95,7 +98,7 @@ export default class Popup extends Component {
} }
onClose() { onClose() {
this.transitionTimeout = null; this.closeTimeout = null;
this.document.body.removeChild(this.popup); this.document.body.removeChild(this.popup);
this.$contentScope.$destroy(); this.$contentScope.$destroy();

View File

@ -9,7 +9,7 @@
} }
} }
&.checked > .btn { &.checked > .btn {
border-color: $color-main; border-color: $color-button;
& > .mark { & > .mark {
position: absolute; position: absolute;
@ -19,7 +19,7 @@
transform: translate(-50%, -50%); transform: translate(-50%, -50%);
width: 10px; width: 10px;
height: 10px; height: 10px;
background-color: $color-main; background-color: $color-button;
} }
} }
&.disabled.checked > .btn > .mark { &.disabled.checked > .btn > .mark {

View File

@ -5,7 +5,7 @@
-webkit-appearance: none; -webkit-appearance: none;
margin-top: -5px; margin-top: -5px;
border-radius: 50%; border-radius: 50%;
background: $color-main; background: $color-button;
border: none; border: none;
height: 12px; height: 12px;
width: 12px; width: 12px;
@ -15,7 +15,7 @@
transition-timing-function: ease-out; transition-timing-function: ease-out;
} }
&:focus::#{$thumb-selector} { &:focus::#{$thumb-selector} {
box-shadow: 0 0 0 10px rgba($color-main, .2); box-shadow: 0 0 0 10px rgba($color-button, .2);
} }
&:active::#{$thumb-selector} { &:active::#{$thumb-selector} {
transform: scale(1.5); transform: scale(1.5);
@ -29,7 +29,7 @@
width: 100%; width: 100%;
height: 3px; height: 3px;
cursor: inherit; cursor: inherit;
background: $color-secondary; background: $color-marginal;
border-radius: 2px; border-radius: 2px;
border: none; border: none;
} }
@ -40,7 +40,7 @@
font-size: 12px; font-size: 12px;
&.main { &.main {
color: $color-main; color: $color-button;
} }
&.min-label { &.min-label {
float: left; float: left;

View File

@ -1,14 +1,26 @@
<form ng-submit="$ctrl.onSubmit()"> <form ng-submit="$ctrl.onSubmit()">
<vn-textfield <vn-textfield
class="dense" class="dense standout"
placeholder="{{::'Search' | translate}}" placeholder="{{::'Search' | translate}}"
ng-model="$ctrl.searchString"> ng-model="$ctrl.searchString">
<prepend> <prepend>
<vn-icon <vn-icon
icon="search" icon="search"
ng-click="$ctrl.clearFilter(); $ctrl.onSubmit()" ng-click="$ctrl.onSubmit()"
pointer> pointer>
</vn-icon> </vn-icon>
<div class="search-params">
<span
ng-repeat="param in $ctrl.params"
class="search-param"
title="{{param.chip}}">
<vn-icon
icon="close"
ng-click="$ctrl.removeParam($index)">
</vn-icon>
{{param.chip}}
</span>
</div>
</prepend> </prepend>
<append> <append>
<vn-icon <vn-icon

View File

@ -1,7 +1,8 @@
import ngModule from '../../module'; import ngModule from '../../module';
import Component from '../../lib/component'; import Component from '../../lib/component';
import './style.scss';
import {buildFilter} from 'vn-loopback/util/filter'; import {buildFilter} from 'vn-loopback/util/filter';
import focus from '../../lib/focus';
import './style.scss';
/** /**
* An input specialized to perform searches, it allows to use a panel * An input specialized to perform searches, it allows to use a panel
@ -11,56 +12,48 @@ import {buildFilter} from 'vn-loopback/util/filter';
* by calling the exprBuilder function for each non-null parameter. * by calling the exprBuilder function for each non-null parameter.
* *
* @property {Object} filter A key-value object with filter parameters * @property {Object} filter A key-value object with filter parameters
* @property {Function} onSearch Function to call when search is submited
* @property {SearchPanel} panel The panel used for advanced searches * @property {SearchPanel} panel The panel used for advanced searches
* @property {CrudModel} model The model used for searching
* @property {Function} exprBuilder If defined, is used to build each non-null param expresion
*/ */
export default class Controller extends Component { export default class Controller extends Component {
constructor($element, $scope, $compile, $state, $transitions) { constructor($element, $) {
super($element, $scope); super($element, $);
this.$element = $element; this.searchState = '.';
this.$compile = $compile;
this.$state = $state;
this.$ = $scope;
let criteria = {to: this.$state.current.name};
this.deregisterCallback = $transitions.onSuccess(criteria,
() => this.onStateChange());
this._filter = null; let criteria = {};
this.autoLoad = false; this.deregisterCallback = this.$transitions.onSuccess(
criteria, () => this.onStateChange());
} }
$postLink() { $postLink() {
if (this.filter === null) this.onStateChange();
this.onStateChange();
} }
set filter(value) { $onDestroy() {
this._filter = value; this.deregisterCallback();
this.$state.go('.', {q: JSON.stringify(value)}, {location: 'replace'});
}
get filter() {
return this._filter;
}
onStateChange() {
this._filter = null;
if (this.$state.params.q) {
try {
this._filter = JSON.parse(this.$state.params.q);
} catch (e) {
console.error(e);
}
}
this.doSearch();
} }
get shownFilter() { get shownFilter() {
return this._filter != null ? this._filter : this.suggestedFilter; return this.filter != null
? this.filter
: this.suggestedFilter;
}
onStateChange() {
if (this.$state.is(this.searchState)) {
if (this.$state.params.q) {
try {
this.filter = JSON.parse(this.$state.params.q);
} catch (e) {
console.error(e);
}
} else
this.filter = null;
focus(this.element.querySelector('vn-textfield input'));
} else
this.filter = null;
this.toBar(this.filter);
} }
openPanel(event) { openPanel(event) {
@ -88,21 +81,143 @@ export default class Controller extends Component {
onPanelSubmit(filter) { onPanelSubmit(filter) {
this.$.popover.hide(); this.$.popover.hide();
filter = compact(filter); filter = compact(filter);
this.filter = filter != null ? filter : {}; filter = filter != null ? filter : {};
this.doSearch(filter);
let element = this.element.querySelector('vn-textfield input');
element.select();
element.focus();
} }
onSubmit() { onSubmit() {
this.filter = this.getObjectFromString(this.searchString); this.doSearch(this.fromBar());
}
removeParam(index) {
this.params.splice(index, 1);
this.doSearch(this.fromBar());
}
get searchString() {
return this._searchString;
}
set searchString(value) {
this._searchString = value;
if (value == null) this.params = [];
}
doSearch(filter) {
let opts = this.$state.is(this.searchState)
? {location: 'replace'} : null;
this.$state.go(this.searchState,
{q: JSON.stringify(filter)}, opts);
}
fromBar() {
let filter = {};
if (this.searchString)
filter.search = this.searchString;
if (this.params) {
for (let param of this.params)
filter[param.key] = param.value;
}
return filter;
}
toBar(filter) {
this.params = [];
this.searchString = filter && filter.search;
if (!filter) return;
let keys = Object.keys(filter);
keys.forEach(key => {
if (key == 'search') return;
let value = filter[key];
let chip;
if (typeof value == 'string'
&& /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/.test(value))
value = new Date(value);
switch (typeof value) {
case 'boolean':
chip = `${value ? '' : 'not '}${key}`;
break;
case 'number':
case 'string':
chip = `${key}: ${value}`;
break;
default:
if (value instanceof Date) {
let format = 'yyyy-MM-dd';
if (value.getHours() || value.getMinutes())
format += ' HH:mm';
chip = `${key}: ${this.$filter('date')(value, format)}`;
} else
chip = key;
}
this.params.push({chip, key, value});
});
}
}
ngModule.vnComponent('vnSearchbar', {
controller: Controller,
template: require('./searchbar.html'),
bindings: {
searchState: '@?',
filter: '<?',
suggestedFilter: '<?',
panel: '@',
info: '@?'
}
});
/**
* @property {CrudModel} model The model used for searching
* @property {Function} exprBuilder If defined, is used to build each non-null param expresion
* @property {Function} onSearch Function to call when search is submited
*/
class AutoSearch {
constructor($state, $transitions) {
this.$state = $state;
this.$transitions = $transitions;
let criteria = {to: this.$state.current.name};
this.deregisterCallback = this.$transitions.onSuccess(criteria,
() => this.onStateChange());
this.fetchFilter();
}
$postLink() {
if (this.filter !== null)
this.doSearch();
}
$onDestroy() {
this.deregisterCallback();
}
fetchFilter() {
if (this.$state.params.q) {
try {
this.filter = JSON.parse(this.$state.params.q);
} catch (e) {
console.error(e);
}
} else
this.filter = null;
}
onStateChange() {
this.fetchFilter();
this.doSearch();
} }
doSearch() { doSearch() {
this.searchString = this.getStringFromObject(this.shownFilter); let filter = this.filter;
let filter = this._filter;
if (filter == null && this.autoload) if (filter == null && this.autoload)
filter = {}; filter = {};
@ -141,94 +256,17 @@ export default class Controller extends Component {
exprBuilder(param, value) { exprBuilder(param, value) {
return {[param]: value}; return {[param]: value};
} }
/**
* Finds pattern key:value or key:(extra value) and passes it to object.
*
* @param {String} searchString The search string
* @return {Object} The parsed object
*/
getObjectFromString(searchString) {
let result = {};
if (searchString) {
let regex = /((([\w_]+):([\w_]+))|([\w_]+):\(([\w_ ]+)\))/gi;
let findPattern = searchString.match(regex);
let remnantString = searchString.replace(regex, '').trim();
if (findPattern) {
for (let i = 0; i < findPattern.length; i++) {
let aux = findPattern[i].split(':');
let property = aux[0];
let value = aux[1].replace(/\(|\)/g, '');
result[property] = value.trim();
}
}
if (remnantString)
result.search = remnantString;
}
return result;
}
/**
* Passes an object to pattern key:value or key:(extra value).
*
* @param {Object} searchObject The search object
* @return {String} The passed string
*/
getStringFromObject(searchObject) {
let search = [];
if (searchObject) {
let keys = Object.keys(searchObject);
keys.forEach(key => {
if (key == 'search') return;
let value = searchObject[key];
let valueString;
if (typeof value === 'string' && value.indexOf(' ') !== -1)
valueString = `(${value})`;
else if (value instanceof Date)
valueString = value.toJSON();
else {
switch (typeof value) {
case 'number':
case 'string':
case 'boolean':
valueString = `${value}`;
}
}
if (valueString)
search.push(`${key}:${valueString}`);
});
if (searchObject.search)
search.unshift(searchObject.search);
}
return search.length ? search.join(' ') : '';
}
$onDestroy() {
this.deregisterCallback();
}
} }
Controller.$inject = ['$element', '$scope', '$compile', '$state', '$transitions']; AutoSearch.$inject = ['$state', '$transitions'];
ngModule.component('vnSearchbar', { ngModule.vnComponent('vnAutoSearch', {
template: require('./searchbar.html'), controller: AutoSearch,
bindings: { bindings: {
filter: '<?',
suggestedFilter: '<?',
onSearch: '&?',
panel: '@',
model: '<?', model: '<?',
onSearch: '&?',
exprBuilder: '&?', exprBuilder: '&?',
paramBuilder: '&?', paramBuilder: '&?'
autoLoad: '<?', }
info: '@?'
},
controller: Controller
}); });
/** /**
@ -251,7 +289,7 @@ function compact(obj) {
} else if (typeof obj == 'object' && obj.constructor == Object) { } else if (typeof obj == 'object' && obj.constructor == Object) {
let keys = Object.keys(obj); let keys = Object.keys(obj);
for (let key of keys) { for (let key of keys) {
if (key.substr(0, 2) == '$$' || compact(obj[key]) === undefined) if (key.charAt(0) == '$' || compact(obj[key]) === undefined)
delete obj[key]; delete obj[key];
} }
if (Object.keys(obj).length == 0) if (Object.keys(obj).length == 0)

View File

@ -2,7 +2,40 @@
vn-searchbar { vn-searchbar {
display: block; display: block;
width: 100%; max-width: 30em;
margin: 0 auto;
.search-params {
flex: 1;
margin: .05em 0;
overflow: visible;
display: flex;
max-width: 16em;
& > .search-param {
color: rgba(0, 0, 0, .6);
background-color: rgba(0, 0, 0, .1);
padding: .1em .4em;
margin-left: .2em;
display: inline-block;
border-radius: .8em;
max-width: 18em;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
& > vn-icon {
font-size: inherit;
vertical-align: middle;
cursor: pointer;
border-radius: 50%;
&:hover {
color: rgba(0, 0, 0, .8);
}
}
}
}
} }
.search-panel { .search-panel {

View File

@ -0,0 +1,81 @@
import ngModule from '../../module';
import './portal';
import './style.scss';
export class Slot {
constructor($element, vnSlotService) {
this.$element = $element;
this.vnSlotService = vnSlotService;
this.$content = null;
}
$onDestroy() {
this.unregister();
}
set name(value) {
this.unregister();
this._name = value;
this.vnSlotService.slots[value] = this;
}
get name() {
return this._name;
}
unregister() {
if (this.name)
this.vnSlotService.slots[this.name] = undefined;
}
setContent($content) {
if (this.$content) {
this.$content.detach();
this.$content = null;
}
this.$content = $content;
if (this.$content) this.$element.append($content);
this.$element[0].style.display = $content ? 'block' : 'none';
}
}
Slot.$inject = ['$element', 'vnSlotService'];
ngModule.vnComponent('vnSlot', {
controller: Slot,
bindings: {
name: '@?'
}
});
export class SlotService {
constructor() {
this.stacks = {};
this.slots = {};
}
push(slot, $transclude) {
if (!this.stacks[slot]) this.stacks[slot] = [];
this.stacks[slot].unshift($transclude);
this.refreshContent(slot);
}
pop(slot) {
let $content = this.stacks[slot].shift();
this.refreshContent(slot);
if ($content && typeof $content == 'object')
$content.remove();
}
refreshContent(slot) {
if (!this.slots[slot]) return;
let $content = this.stacks[slot][0];
if (typeof $content == 'function') {
$content(clone => {
$content = this.stacks[slot][0] = clone;
});
}
this.slots[slot].setContent($content);
}
}
ngModule.service('vnSlotService', SlotService);

View File

@ -0,0 +1,28 @@
import ngModule from '../../module';
/**
* Component used to fill slots with content.
*/
export default class Portal {
constructor($transclude, vnSlotService) {
this.$transclude = $transclude;
this.vnSlotService = vnSlotService;
}
$postLink() {
this.vnSlotService.push(this.slot, this.$transclude);
}
$onDestroy() {
this.vnSlotService.pop(this.slot);
}
}
Portal.$inject = ['$transclude', 'vnSlotService'];
ngModule.component('vnPortal', {
controller: Portal,
transclude: true,
bindings: {
slot: '@'
}
});

View File

@ -0,0 +1,3 @@
vn-slot {
display: block;
}

View File

@ -6,6 +6,7 @@
cy="50" cy="50"
r="20" r="20"
fill="none" fill="none"
stroke="currentColor"
stroke-miterlimit="10"> stroke-miterlimit="10">
</circle> </circle>
</svg> </svg>

View File

@ -4,6 +4,7 @@ vn-spinner {
display: inline-block; display: inline-block;
min-height: 28px; min-height: 28px;
min-width: 28px; min-width: 28px;
color: $color-main;
& > .loader { & > .loader {
position: relative; position: relative;
@ -29,7 +30,6 @@ vn-spinner {
margin: auto; margin: auto;
& > .path { & > .path {
stroke: $color-main;
stroke-dasharray: 1, 200; stroke-dasharray: 1, 200;
stroke-dashoffset: 0; stroke-dashoffset: 0;
stroke-linecap: square; stroke-linecap: square;

View File

@ -3,8 +3,8 @@
vn-table { vn-table {
display: block; display: block;
overflow: auto;
width: 100%; width: 100%;
// overflow: auto;
} }
.vn-table { .vn-table {
width: 100%; width: 100%;

View File

@ -40,10 +40,10 @@
background-color: rgba(0, 0, 0, .1); background-color: rgba(0, 0, 0, .1);
} }
&.checked > .btn { &.checked > .btn {
border-color: $color-main; border-color: $color-button;
& > .focus-mark { & > .focus-mark {
background-color: rgba($color-main, .15); background-color: rgba($color-button, .15);
} }
} }
&.disabled { &.disabled {

View File

@ -20,7 +20,7 @@
background-color: rgba(0, 0, 0, .05); background-color: rgba(0, 0, 0, .05);
&.marked { &.marked {
background: $color-main; background: $color-button;
color: $color-font-dark; color: $color-font-dark;
} }
} }

View File

@ -12,14 +12,23 @@ export function directive() {
restrict: 'A', restrict: 'A',
link: function($scope, $element, $attrs) { link: function($scope, $element, $attrs) {
let id = kebabToCamel($attrs.vnId); let id = kebabToCamel($attrs.vnId);
let $ctrl = $element[0].$ctrl
? $element[0].$ctrl
: $element.controller($element[0].tagName.toLowerCase());
if (!id) if (!id)
throw new Error(`vnId: Attribute can't be null`); throw new Error(`vnId: Attribute can't be null`);
$scope[id] = $ctrl || $element[0]; let $ctrl = $element[0].$ctrl
? $element[0].$ctrl
: $element.controller($element[0].tagName.toLowerCase());
let ctrl = $ctrl || $element[0];
$scope[id] = ctrl;
if (!$scope.hasOwnProperty('$ctrl')) {
while ($scope && !$scope.hasOwnProperty('$ctrl'))
$scope = Object.getPrototypeOf($scope);
if ($scope) $scope[id] = ctrl;
}
} }
}; };
} }

View File

@ -107,6 +107,7 @@ function runFn(
$compile, $compile,
$filter, $filter,
$interpolate, $interpolate,
$window,
vnApp) { vnApp) {
Object.assign(Component.prototype, { Object.assign(Component.prototype, {
$translate, $translate,
@ -119,6 +120,7 @@ function runFn(
$compile, $compile,
$filter, $filter,
$interpolate, $interpolate,
$window,
vnApp vnApp
}); });
} }
@ -133,6 +135,7 @@ runFn.$inject = [
'$compile', '$compile',
'$filter', '$filter',
'$interpolate', '$interpolate',
'$window',
'vnApp' 'vnApp'
]; ];

16
front/core/lib/focus.js Normal file
View File

@ -0,0 +1,16 @@
import isMobile from './is-mobile';
export default function focus(element) {
if (isMobile) return;
setTimeout(() => element.focus(), 10);
}
export function select(element) {
if (isMobile) return;
setTimeout(() => {
element.focus();
if (element.select)
element.select();
}, 10);
}

View File

@ -2,8 +2,9 @@ import './module-loader';
import './crud'; import './crud';
import './copy'; import './copy';
import './equals'; import './equals';
import './focus';
import './get-main-route';
import './modified'; import './modified';
import './key-codes'; import './key-codes';
import './http-error'; import './http-error';
import './user-error'; import './user-error';
import './get-main-route';

View File

@ -1,9 +1,6 @@
import {ng, ngDeps} from './vendor'; import {ng, ngDeps} from './vendor';
import {camelToKebab} from './lib/string'; import {camelToKebab} from './lib/string';
const ngModule = ng.module('vnCore', ngDeps);
export default ngModule;
/** /**
* Acts like native Module.component() function but merging component options * Acts like native Module.component() function but merging component options
* with parent component options. This method establishes the $options property * with parent component options. This method establishes the $options property
@ -17,7 +14,7 @@ export default ngModule;
* @param {Object} options The component options * @param {Object} options The component options
* @return {angularModule} The same angular module * @return {angularModule} The same angular module
*/ */
ngModule.vnComponent = function(name, options) { function vnComponent(name, options) {
let controller = options.controller; let controller = options.controller;
let parent = Object.getPrototypeOf(controller); let parent = Object.getPrototypeOf(controller);
let parentOptions = parent.$options || {}; let parentOptions = parent.$options || {};
@ -57,10 +54,21 @@ ngModule.vnComponent = function(name, options) {
controller.$classNames = classNames; controller.$classNames = classNames;
return this.component(name, mergedOptions); return this.component(name, mergedOptions);
}
const ngModuleFn = ng.module;
ng.module = function(...args) {
let ngModule = ngModuleFn.apply(this, args);
ngModule.vnComponent = vnComponent;
return ngModule;
}; };
config.$inject = ['$translateProvider', '$translatePartialLoaderProvider']; const ngModule = ng.module('vnCore', ngDeps);
export function config($translateProvider, $translatePartialLoaderProvider) { export default ngModule;
config.$inject = ['$translateProvider', '$translatePartialLoaderProvider', '$animateProvider'];
export function config($translateProvider, $translatePartialLoaderProvider, $animateProvider) {
// For CSS browser targeting // For CSS browser targeting
document.documentElement.setAttribute('data-browser', navigator.userAgent); document.documentElement.setAttribute('data-browser', navigator.userAgent);
@ -91,5 +99,8 @@ export function config($translateProvider, $translatePartialLoaderProvider) {
return langAliases[locale]; return langAliases[locale];
return fallbackLang; return fallbackLang;
}); });
$animateProvider.customFilter(
node => node.tagName == 'UI-VIEW');
} }
ngModule.config(config); ngModule.config(config);

View File

@ -13,11 +13,15 @@ function interceptor($q, vnApp, vnToken, $translate) {
vnApp.pushLoader(); vnApp.pushLoader();
if (config.url.charAt(0) !== '/' && apiPath) if (config.url.charAt(0) !== '/' && apiPath)
config.url = `${apiPath}/${config.url}`; config.url = `${apiPath}${config.url}`;
if (vnToken.token) if (vnToken.token)
config.headers.Authorization = vnToken.token; config.headers.Authorization = vnToken.token;
if ($translate.use()) if ($translate.use())
config.headers['Accept-Language'] = $translate.use(); config.headers['Accept-Language'] = $translate.use();
if (config.filter) {
if (!config.params) config.params = {};
config.params.filter = config.filter;
}
return config; return config;
}, },

View File

@ -40,4 +40,8 @@ button {
a { a {
color: $color-font-link; color: $color-font-link;
text-decoration: none; text-decoration: none;
}
.ng-leave,
.ng-enter {
transition: none;
} }

View File

@ -19,27 +19,36 @@ $spacing-xs: 4px;
$spacing-sm: 8px; $spacing-sm: 8px;
$spacing-md: 16px; $spacing-md: 16px;
$spacing-lg: 32px; $spacing-lg: 32px;
$spacing-xl: 100px; $spacing-xl: 70px;
// Light theme // Light theme
$color-header: #3d3d3d; $color-primary: #f7931e;
$color-bg: #e5e5e5; $color-secondary: $color-primary;
$color-bg-dark: #3d3d3d;
$color-font: #222; $color-font: #222;
$color-font-light: #555; $color-font-light: #555;
$color-font-secondary: #9b9b9b; $color-font-secondary: #9b9b9b;
$color-font-dark: white; $color-font-dark: white;
$color-font-bg: rgba(0, 0, 0, .7);
$color-font-link: #005a9a; $color-font-link: #005a9a;
$color-active: #3d3d3d; $color-font-bg: rgba(0, 0, 0, .7);
$color-active-font: white; $color-font-bg-marginal: rgba(0, 0, 0, .4);
$color-font-bg-dark: rgba(255, 255, 255, .7);
$color-font-bg-dark-marginal: rgba(255, 255, 255, .4);
$color-header: darken($color-primary, 5%);
$color-menu-header: #3d3d3d;
$color-bg: #e5e5e5;
$color-bg-dark: #3d3d3d;
$color-active: $color-primary;
$color-active-font: $color-font-dark;
$color-bg-panel: white; $color-bg-panel: white;
$color-main: #f7931e; $color-main: $color-primary;
$color-secondary: #ccc; $color-marginal: #ccc;
$color-success: #a3d131; $color-success: #a3d131;
$color-notice: #32b1ce; $color-notice: #32b1ce;
$color-alert: #f42121; $color-alert: #f42121;
$color-button: $color-secondary;
$color-spacer: rgba(0, 0, 0, .3); $color-spacer: rgba(0, 0, 0, .3);
$color-spacer-light: rgba(0, 0, 0, .12); $color-spacer-light: rgba(0, 0, 0, .12);
@ -78,7 +87,7 @@ $color-active: #666;
$color-active-font: white; $color-active-font: white;
$color-bg-panel: #3c3b3b; $color-bg-panel: #3c3b3b;
$color-main: #f7931e; $color-main: #f7931e;
$color-secondary: #ccc; $color-marginal: #ccc;
$color-success: #a3d131; $color-success: #a3d131;
$color-notice: #32b1ce; $color-notice: #32b1ce;
$color-alert: #f42121; $color-alert: #f42121;

View File

@ -2,6 +2,7 @@ import '@babel/polyfill';
import * as ng from 'angular'; import * as ng from 'angular';
export {ng}; export {ng};
import 'angular-animate';
import 'angular-translate'; import 'angular-translate';
import 'angular-translate-loader-partial'; import 'angular-translate-loader-partial';
import '@uirouter/angularjs'; import '@uirouter/angularjs';
@ -9,6 +10,7 @@ import 'mg-crud';
import 'oclazyload'; import 'oclazyload';
export const ngDeps = [ export const ngDeps = [
'ngAnimate',
'pascalprecht.translate', 'pascalprecht.translate',
'ui.router', 'ui.router',
'mgCrud', 'mgCrud',

View File

@ -31,6 +31,11 @@
"resolved": "https://registry.npmjs.org/angular/-/angular-1.7.5.tgz", "resolved": "https://registry.npmjs.org/angular/-/angular-1.7.5.tgz",
"integrity": "sha512-760183yxtGzni740IBTieNuWLtPNAoMqvmC0Z62UoU0I3nqk+VJuO3JbQAXOyvo3Oy/ZsdNQwrSTh/B0OQZjNw==" "integrity": "sha512-760183yxtGzni740IBTieNuWLtPNAoMqvmC0Z62UoU0I3nqk+VJuO3JbQAXOyvo3Oy/ZsdNQwrSTh/B0OQZjNw=="
}, },
"angular-animate": {
"version": "1.7.8",
"resolved": "https://registry.npmjs.org/angular-animate/-/angular-animate-1.7.8.tgz",
"integrity": "sha512-bINtzizq7TbJzfVrDpwLfTxVl0Qd7fRNWFb5jKYI1vaFZobQNX/QONXlLow6ySsDbZ6eLECycB7mvWtfh1YYaw=="
},
"angular-translate": { "angular-translate": {
"version": "2.18.1", "version": "2.18.1",
"resolved": "https://registry.npmjs.org/angular-translate/-/angular-translate-2.18.1.tgz", "resolved": "https://registry.npmjs.org/angular-translate/-/angular-translate-2.18.1.tgz",

View File

@ -12,6 +12,7 @@
"@babel/polyfill": "^7.2.5", "@babel/polyfill": "^7.2.5",
"@uirouter/angularjs": "^1.0.20", "@uirouter/angularjs": "^1.0.20",
"angular": "^1.7.5", "angular": "^1.7.5",
"angular-animate": "^1.7.8",
"angular-translate": "^2.18.1", "angular-translate": "^2.18.1",
"angular-translate-loader-partial": "^2.18.1", "angular-translate-loader-partial": "^2.18.1",
"js-yaml": "^3.13.1", "js-yaml": "^3.13.1",

View File

@ -1,29 +1,4 @@
<vn-topbar ng-if="$ctrl.showTopbar"> <vn-layout ng-if="$ctrl.showLayout"></vn-layout>
<a class="logo" ui-sref="home" title="{{'Home' | translate}}"> <ui-view name="login"></ui-view>
<img src="./logo.svg" alt="Logo"></img>
</a>
<vn-icon
icon="menu"
class="show-menu"
ng-if="$ctrl.leftMenu"
ng-click="$ctrl.leftMenu.show()">
</vn-icon>
<div class="main-title" translate>
{{$ctrl.$state.current.description}}
</div>
<vn-spinner enable="$ctrl.vnApp.loading"></vn-spinner>
<vn-main-menu></vn-main-menu>
<vn-icon
icon="menu"
class="show-menu"
ng-if="$ctrl.rightMenu"
ng-click="$ctrl.rightMenu.show()">
</vn-icon>
</vn-topbar>
<div ui-view
class="main-view"
ng-class="{padding: $ctrl.showTopbar}">
<vn-home></vn-home>
</div>
<vn-snackbar vn-id="snackbar"></vn-snackbar> <vn-snackbar vn-id="snackbar"></vn-snackbar>
<vn-debug-info></vn-debug-info> <vn-debug-info></vn-debug-info>

View File

@ -1,5 +1,6 @@
import ngModule from '../../module'; import ngModule from '../../module';
import './style.scss'; import './style.scss';
import Component from 'core/lib/component';
/** /**
* The main graphical application component. * The main graphical application component.
@ -7,28 +8,21 @@ import './style.scss';
* @property {SideMenu} leftMenu The left menu, if it's present * @property {SideMenu} leftMenu The left menu, if it's present
* @property {SideMenu} rightMenu The left menu, if it's present * @property {SideMenu} rightMenu The left menu, if it's present
*/ */
export default class App { export default class App extends Component {
constructor($, $state, vnApp) {
Object.assign(this, {
$,
$state,
vnApp
});
}
$postLink() { $postLink() {
this.vnApp.logger = this; this.vnApp.logger = this;
} }
$onDestroy() { get showLayout() {
this.vnApp.logger = null;
}
get showTopbar() {
let state = this.$state.current.name; let state = this.$state.current.name;
return state && state != 'login'; return state && state != 'login';
} }
$onDestroy() {
this.deregisterCallback();
this.vnApp.logger = null;
}
showMessage(message) { showMessage(message) {
this.$.snackbar.show({message: message}); this.$.snackbar.show({message: message});
} }
@ -41,9 +35,8 @@ export default class App {
this.$.snackbar.showError({message: message}); this.$.snackbar.showError({message: message});
} }
} }
App.$inject = ['$scope', '$state', 'vnApp'];
ngModule.component('vnApp', { ngModule.vnComponent('vnApp', {
template: require('./app.html'), template: require('./app.html'),
controller: App controller: App
}); });

View File

@ -1,99 +1,21 @@
@import "variables"; @import "variables";
vn-app { vn-app {
height: inherit;
display: block; display: block;
height: inherit;
& > vn-topbar { ui-view {
position: fixed; display: block;
top: 0;
left: 0;
width: 100%;
z-index: 10;
box-shadow: 0 .1em .2em $color-shadow;
height: $topbar-height;
padding: .4em;
& > header {
& > * {
padding: .3em;
}
& > .logo > img {
height: 1.4em;
display: block;
}
& > .show-menu {
display: none;
font-size: 1.8em;
cursor: pointer;
&:hover {
color: $color-main;
}
}
& > .main-title {
font-size: 1.6em;
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
padding-left: .6em;
}
& > vn-spinner {
padding: 0 .4em;
}
& > vn-main-menu {
flex: 1;
}
}
}
& > .main-view {
box-sizing: border-box; box-sizing: border-box;
height: inherit;
&.padding {
padding-top: $topbar-height;
}
.content-block {
box-sizing: border-box;
padding: $spacing-md;
height: inherit;
}
vn-main-block {
display: block;
margin: 0 auto;
padding-left: $menu-width;
height: 100%
}
.main-with-right-menu {
padding-right: $menu-width;
@media screen and (max-width: 800px) {
padding-right: 0;
}
}
}
@media screen and (max-width: $mobile-width) { &.ng-enter {
& > vn-topbar > header { transition: all 200ms ease-out;
& > .logo { transform: translate3d(-2em, 0, 0);
display: none; opacity: 0;
}
& > .show-menu {
display: block;
}
} }
& > .main-view { &.ng-enter.ng-enter-active {
.content-block { transform: translate3d(0, 0, 0);
margin-left: 0; opacity: 1;
margin-right: 0;
}
vn-main-block {
padding-left: 0;
}
.main-with-right-menu {
padding-right: 0;
}
} }
} }
} }

View File

@ -50,7 +50,7 @@
& > vn-icon { & > vn-icon {
padding: $spacing-sm; padding: $spacing-sm;
color: $color-secondary; color: $color-marginal;
font-size: 1.5em; font-size: 1.5em;
&.bright { &.bright {

View File

@ -1,4 +1,4 @@
<div ng-if="$ctrl.$state.current.name === 'home'"> <div>
<div class="modules"> <div class="modules">
<a <a
ng-repeat="mod in ::$ctrl.modules" ng-repeat="mod in ::$ctrl.modules"

View File

@ -23,7 +23,7 @@ vn-home {
@extend %clickable-light; @extend %clickable-light;
overflow:hidden; overflow:hidden;
border-radius: 6px; border-radius: 6px;
background-color: $color-main; background-color: $color-button;
color: $color-font-dark; color: $color-font-dark;
display: flex; display: flex;
flex-direction: column; flex-direction: column;

View File

@ -2,10 +2,13 @@ import './app/app';
import './background/background'; import './background/background';
import './descriptor'; import './descriptor';
import './home/home'; import './home/home';
import './layout';
import './left-menu/left-menu'; import './left-menu/left-menu';
import './login/login'; import './login/login';
import './main-menu/main-menu'; import './main-menu/main-menu';
import './topbar/topbar'; import './module-card';
import './module-main';
import './side-menu/side-menu'; import './side-menu/side-menu';
import './summary'; import './summary';
import './topbar/topbar';
import './user-popover'; import './user-popover';

View File

@ -0,0 +1,69 @@
<vn-topbar>
<vn-icon-button
icon="menu"
class="show-menu"
ng-if="$ctrl.leftMenu"
ng-click="$ctrl.leftMenu.show()">
</vn-icon-button>
<div class="start">
<div class="main-title" translate>
{{$ctrl.$state.current.description}}
</div>
<vn-spinner
ng-if="$ctrl.vnApp.loading"
enable="true">
</vn-spinner>
</div>
<vn-slot name="topbar"></vn-slot>
<div class="end">
<vn-icon-button
id="apps"
icon="apps"
vn-popover="apps-menu"
translate-attr="{title: 'Applications'}">
</vn-icon-button>
<vn-icon-button
icon="menu"
class="show-menu"
ng-if="$ctrl.rightMenu"
ng-click="$ctrl.rightMenu.show()">
</vn-icon-button>
</div>
<vn-menu vn-id="apps-menu">
<ul class="modules-menu vn-pa-sm">
<li
ng-repeat="mod in ::$ctrl.modules"
ui-sref="{{::mod.route.state}}">
<vn-icon icon="{{::mod.icon || 'photo'}}"></vn-icon>
<span translate>{{::mod.name}}</span>
</li>
</ul>
</vn-menu>
</vn-topbar>
<vn-side-menu side="left">
<div class="header">
<a class="logo" ui-sref="home" title="{{'Home' | translate}}">
<img src="./logo.svg" alt="Logo"></img>
</a>
<vn-main-menu
class="vn-pt-md">
</vn-main-menu>
</div>
<vn-slot name="menu"></vn-slot>
</vn-side-menu>
<vn-portal slot="menu">
<vn-list class="vn-py-md">
<a
ng-repeat="mod in ::$ctrl.modules"
class="vn-item"
ui-sref="{{::mod.route.state}}">
<vn-item-section avatar>
<vn-icon icon="{{::mod.icon || 'photo'}}"></vn-icon>
</vn-item-section>
<vn-item-section translate>
{{::mod.name}}
</vn-item-section>
</a>
</vn-list>
</vn-portal>
<ui-view class="main-view"></ui-view>

View File

@ -0,0 +1,19 @@
import ngModule from '../../module';
import Component from 'core/lib/component';
import './style.scss';
export class Layout extends Component {
constructor($element, $, vnModules) {
super($element, $);
this.modules = vnModules.get();
}
}
Layout.$inject = ['$element', '$scope', 'vnModules'];
ngModule.component('vnLayout', {
template: require('./index.html'),
controller: Layout,
require: {
app: '^vnApp'
}
});

View File

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 11 KiB

View File

@ -0,0 +1,121 @@
@import "variables";
vn-layout {
& > vn-topbar {
position: fixed;
top: 0;
right: 0;
left: 0;
z-index: 10;
box-shadow: 0 .1em .2em $color-shadow;
height: $topbar-height;
padding: 0 1em;
justify-content: space-between;
& > .start {
flex: 1;
display: flex;
align-items: center;
overflow: hidden;
padding-right: 1em;
& > .main-title {
font-size: 1.6em;
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
padding-left: .4em;
}
& > vn-spinner {
padding: 0 .4em;
color: $color-font-dark;
}
}
& > vn-slot {
flex: auto;
}
& > .end {
flex: 1;
padding-left: 1em;
align-items: center;
justify-content: flex-end;
display: flex;
}
.vn-button {
color: inherit;
font-size: 1.05em;
padding: 0;
}
.show-menu {
display: none;
}
}
& > vn-side-menu > .menu {
display: flex;
flex-direction: column;
align-items: stretch;
& > .header {
background-color: $color-menu-header;
padding: $spacing-md;
color: $color-font-dark;
& > .logo > img {
width: 100%;
display: block;
}
}
& > vn-slot {
flex: 1;
overflow: auto;
}
}
&.left-menu {
& > vn-topbar {
left: $menu-width;
}
& > .main-view {
padding-left: $menu-width;
}
}
&.right-menu {
& > .main-view {
padding-right: $menu-width;
}
}
& > .main-view {
padding-top: $topbar-height;
}
ui-view {
height: inherit;
& > * {
display: block;
padding: $spacing-md;
}
}
@media screen and (max-width: $mobile-width) {
& > vn-topbar {
& > .show-menu {
display: block;
}
}
&.left-menu {
& > vn-topbar {
left: 0;
}
& > .main-view {
padding-left: 0;
}
}
&.right-menu {
& > .main-view {
padding-right: 0;
}
}
ui-view > * {
padding-left: 0;
padding-right: 0;
}
}
}

View File

@ -1,17 +1,30 @@
<ul ng-if="::$ctrl.items.length > 0" class="vn-py-md"> <ul class="vn-list" ng-if="::$ctrl.items.length > 0">
<li ng-repeat="item in ::$ctrl.items" name="{{::item.description}}"> <li ng-repeat="item in ::$ctrl.items" name="{{::item.description}}">
<a ui-sref="{{::item.state}}" <a ui-sref="{{::item.state}}"
ng-class="{active: item.active && !item.childs, expanded: item.active, collapsed: !item.active}" class="vn-item"
ng-class="{active: item.active && !item.childs, expanded: item.active}"
ng-click="$ctrl.setActive(item)"> ng-click="$ctrl.setActive(item)">
<vn-icon icon="{{::item.icon}}" ng-if="::item.icon"></vn-icon> <vn-item-section avatar>
<vn-icon icon="keyboard_arrow_down" ng-if="::item.childs.length > 0"></vn-icon> <vn-icon icon="{{::item.icon}}" ng-if="::item.icon"></vn-icon>
<span translate>{{::item.description}}</span> </vn-item-section>
<vn-item-section translate>
{{::item.description}}
</vn-item-section>
<vn-item-section side>
<vn-icon icon="keyboard_arrow_down" ng-if="::item.childs.length > 0"></vn-icon>
</vn-item-section>
</a> </a>
<ul ng-show="item.childs.length > 0 && item.active"> <ul class="vn-list" ng-show="item.childs.length > 0 && item.active">
<li ng-repeat="child in ::item.childs"> <li ng-repeat="child in ::item.childs">
<a ui-sref="{{::child.state}}" ng-class="{active: child.active}"> <a ui-sref="{{::child.state}}"
<vn-icon icon="{{::child.icon}}"></vn-icon> class="vn-item"
<span translate>{{::child.description}}</span> ng-class="{active: child.active}">
<vn-item-section avatar>
<vn-icon icon="{{::child.icon}}"></vn-icon>
</vn-item-section>
<vn-item-section translate>
{{::child.description}}
</vn-item-section>
</a> </a>
</li> </li>
</ul> </ul>

View File

@ -6,24 +6,42 @@ export default class LeftMenu {
this.$element = $element; this.$element = $element;
this.$timeout = $timeout; this.$timeout = $timeout;
this.$state = $state; this.$state = $state;
this.aclService = aclService;
this.deregisterCallback = $transitions.onSuccess({}, this.deregisterCallback = $transitions.onSuccess({},
() => this.activateItem()); () => this.activateItem());
this.source = 'main';
this._depth = 3; this._depth = 3;
}
$onInit() {
this.items = this.fetchItems();
this.activateItem();
}
set depth(value) {
this._depth = value;
this.activateItem();
}
get depth() {
return this._depth;
}
fetchItems() {
let states = this.$state.router.stateRegistry.states; let states = this.$state.router.stateRegistry.states;
let moduleIndex = this.$state.current.data.moduleIndex; let moduleIndex = this.$state.current.data.moduleIndex;
let moduleFile = window.routes[moduleIndex] || []; let moduleFile = window.routes[moduleIndex] || [];
let menu = moduleFile.menu || []; let menu = moduleFile.menus && moduleFile.menus[this.source] || [];
let items = []; let items = [];
function addItem(items, item) { let addItem = (items, item) => {
let state = states[item.state]; let state = states[item.state];
if (!state) return; if (!state) return;
state = state.self; state = state.self;
let acl = state.data.acl; let acl = state.data.acl;
if (acl && !aclService.hasAny(acl)) if (acl && !this.aclService.hasAny(acl))
return; return;
items.push({ items.push({
@ -31,7 +49,7 @@ export default class LeftMenu {
description: state.description, description: state.description,
state: item.state state: item.state
}); });
} };
for (let item of menu) { for (let item of menu) {
if (item.state) if (item.state)
@ -52,17 +70,7 @@ export default class LeftMenu {
} }
} }
this.items = items; return items;
this.activateItem();
}
set depth(value) {
this._depth = value;
this.activateItem();
}
get depth() {
return this._depth;
} }
activateItem() { activateItem() {
@ -93,9 +101,8 @@ export default class LeftMenu {
item.active = !item.active; item.active = !item.active;
this.$timeout(() => { this.$timeout(() => {
let element = this.$element[0].querySelector('a[class="expanded"]'); let element = this.$element[0].querySelector('a.expanded');
if (element) if (element) element.scrollIntoView();
element.scrollIntoView();
}); });
} }
@ -109,6 +116,7 @@ ngModule.component('vnLeftMenu', {
template: require('./left-menu.html'), template: require('./left-menu.html'),
controller: LeftMenu, controller: LeftMenu,
bindings: { bindings: {
source: '@?',
depth: '<?' depth: '<?'
} }
}); });

View File

@ -1,53 +1,18 @@
@import "effects"; @import "effects";
@import "variables";
vn-left-menu { vn-left-menu {
ul { & > .vn-list {
margin: 0; padding: $spacing-md 0;
padding: 0;
width: 100%;
& > li { & > li > .vn-item {
list-style: none; & > [side] > vn-icon[icon="keyboard_arrow_down"] {
display: block; transition: transform 200ms;
& > ul > li > a {
padding-left: 2em
} }
} &.expanded > [side] > vn-icon[icon="keyboard_arrow_down"] {
& > li > a {
@extend %clickable;
padding: .5em 1em;
display: block;
color: inherit;
& > vn-icon:nth-child(1) {
vertical-align: middle;
margin-right: .4em
}
& > vn-icon:nth-child(2) {
float: right;
margin-left: .4em
}
}
& > li > a.active {
@extend %active;
}
& > li > a.expanded {
& > vn-icon[icon="keyboard_arrow_down"] {
transition: all 0.2s;
transform: rotate(180deg); transform: rotate(180deg);
} }
} }
& > li > a.collapsed {
& > vn-icon[icon="keyboard_arrow_down"] {
transition: all 0.2s;
transform: rotate(0deg);
}
}
} }
} }

View File

@ -14,6 +14,7 @@ export default class Controller {
remember: true remember: true
}); });
} }
submit() { submit() {
this.loading = true; this.loading = true;
this.vnAuth.login(this.user, this.password, this.remember) this.vnAuth.login(this.user, this.password, this.remember)
@ -28,6 +29,7 @@ export default class Controller {
throw err; throw err;
}); });
} }
focusUser() { focusUser() {
this.$.userField.select(); this.$.userField.select();
this.$.userField.focus(); this.$.userField.focus();

View File

@ -1,31 +1,15 @@
<div> <div
<div ng-click="userPopover.show($event)"
ng-click="userPopover.show($event)" id="user"
id="user" class="unselectable">
class="unselectable"> {{$root.user.nickname}}
{{$root.user.nickname}}
</div>
<vn-icon-button
id="apps"
icon="apps"
vn-popover="apps-menu"
translate-attr="{title: 'Applications'}">
</vn-icon-button>
<vn-icon-button
id="logout"
icon="exit_to_app"
translate-attr="{title: 'Logout'}"
ng-click="$ctrl.vnAuth.logout()">
</vn-icon-button>
</div> </div>
<vn-menu vn-id="apps-menu"> <vn-icon-button
<ul class="modules-menu vn-pa-sm"> id="logout"
<li ng-repeat="mod in ::$ctrl.modules" ui-sref="{{::mod.route.state}}"> icon="exit_to_app"
<vn-icon icon="{{::mod.icon || 'photo'}}"></vn-icon> translate-attr="{title: 'Logout'}"
<span translate>{{::mod.name}}</span> ng-click="$ctrl.vnAuth.logout()">
</li> </vn-icon-button>
</ul>
</vn-menu>
<vn-user-popover <vn-user-popover
vn-id="user-popover"> vn-id="user-popover">
</vn-user-popover> </vn-user-popover>

View File

@ -2,12 +2,11 @@ import ngModule from '../../module';
import './style.scss'; import './style.scss';
export default class MainMenu { export default class MainMenu {
constructor($, $http, vnAuth, vnModules) { constructor($, $http, vnAuth) {
Object.assign(this, { Object.assign(this, {
$, $,
$http, $http,
vnAuth, vnAuth
modules: vnModules.get()
}); });
} }
@ -22,7 +21,7 @@ export default class MainMenu {
}); });
} }
} }
MainMenu.$inject = ['$scope', '$http', 'vnAuth', 'vnModules']; MainMenu.$inject = ['$scope', '$http', 'vnAuth'];
ngModule.component('vnMainMenu', { ngModule.component('vnMainMenu', {
template: require('./main-menu.html'), template: require('./main-menu.html'),

View File

@ -4,35 +4,30 @@
vn-main-menu { vn-main-menu {
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: flex-end; justify-content: space-between;
box-sizing: border-box;
& > div { & > * {
display: flex; transition: color 250ms ease-out;
align-items: center; }
box-sizing: border-box; & > #user {
flex: 1;
& > * { vertical-align: middle;
transition: color 250ms ease-out; font-weight: bold;
} margin-right: .2em;
& > #user { text-overflow: ellipsis;
vertical-align: middle; white-space: nowrap;
font-weight: bold; overflow: hidden;
margin-right: .2em; cursor: pointer;
text-overflow: ellipsis; }
white-space: nowrap; & > .vn-button {
overflow: hidden; height: initial;
cursor: pointer; color: inherit;
} padding: 0;
& > .vn-button { }
font-size: 1.2em; & > :hover {
color: inherit; color: $color-main;
padding: 0; opacity: 1;
margin-left: .3em;
}
& > :hover {
color: $color-main;
opacity: 1;
}
} }
} }

View File

@ -0,0 +1,29 @@
import ngModule from '../../module';
import Component from 'core/lib/component';
import './style.scss';
/**
* Base class for module cards.
*/
export default class ModuleCard extends Component {
constructor($element, $) {
super($element, $);
this.element.classList.add('vn-module-card');
}
$onInit() {
this.reload();
}
/**
* Reloads the card data. Should be implemented or overriden by child
* classes.
*/
reload() {
throw new Error('ModuleCard::reload() method not implemented');
}
}
ngModule.vnComponent('vnModuleCard', {
controller: ModuleCard
});

View File

@ -0,0 +1,5 @@
@import "variables";
.vn-module-card {
padding: 0;
}

View File

@ -0,0 +1,4 @@
<vn-portal slot="menu">
<vn-left-menu></vn-left-menu>
</vn-portal>
<ui-view></ui-view>

View File

@ -0,0 +1,15 @@
import ngModule from '../../module';
import Component from 'core/lib/component';
import './style.scss';
export default class ModuleMain extends Component {
constructor($element, $) {
super($element, $);
this.element.classList.add('vn-module-main');
}
}
ngModule.vnComponent('vnModuleMain', {
template: require('./index.html'),
controller: ModuleMain
});

View File

@ -0,0 +1,5 @@
@import "variables";
.vn-module-main {
padding: 0;
}

View File

@ -1,4 +1,5 @@
import ngModule from '../../module'; import ngModule from '../../module';
import Component from 'core/lib/component';
import './style.scss'; import './style.scss';
/** /**
@ -7,15 +8,10 @@ import './style.scss';
* @property {String} side [left|right] The side where the menu is displayed * @property {String} side [left|right] The side where the menu is displayed
* @property {Boolean} shown Whether the menu it's currently displayed (Only for small viewports) * @property {Boolean} shown Whether the menu it's currently displayed (Only for small viewports)
*/ */
export default class SideMenu { export default class SideMenu extends Component {
constructor($, $element, $window, $transitions) { constructor($element, $) {
Object.assign(this, { super($element, $);
$, this.side = 'left';
$element,
$window,
$transitions,
side: 'left'
});
} }
$onInit() { $onInit() {
@ -23,18 +19,25 @@ export default class SideMenu {
if (this.side == 'right') { if (this.side == 'right') {
this.menu.classList.add('right'); this.menu.classList.add('right');
this.app.rightMenu = this; this.layout.rightMenu = this;
this.layout.element.classList.add('right-menu');
} else { } else {
this.menu.classList.add('left'); this.menu.classList.add('left');
this.app.leftMenu = this; this.layout.leftMenu = this;
this.layout.element.classList.add('left-menu');
} }
} }
$onDestroy() { $onDestroy() {
if (this.side == 'right') if (this.side == 'right') {
this.app.rightMenu = null; this.layout.rightMenu = null;
else this.layout.element.classList.remove('right-menu');
this.app.leftMenu = null; } else {
// this.layout.leftMenu = null;
this.layout.element.classList.remove('left-menu');
}
this.hide();
} }
onEscape(event) { onEscape(event) {
@ -50,6 +53,7 @@ export default class SideMenu {
} }
show() { show() {
if (this.shown) return;
this.shown = true; this.shown = true;
this.handler = e => this.onEscape(e); this.handler = e => this.onEscape(e);
this.$window.addEventListener('keydown', this.handler); this.$window.addEventListener('keydown', this.handler);
@ -57,12 +61,12 @@ export default class SideMenu {
} }
hide() { hide() {
if (!this.shown) return;
this.$window.removeEventListener('keydown', this.handler); this.$window.removeEventListener('keydown', this.handler);
this.stateHandler(); this.stateHandler();
this.shown = false; this.shown = false;
} }
} }
SideMenu.$inject = ['$scope', '$element', '$window', '$transitions'];
ngModule.component('vnSideMenu', { ngModule.component('vnSideMenu', {
template: require('./side-menu.html'), template: require('./side-menu.html'),
@ -72,6 +76,6 @@ ngModule.component('vnSideMenu', {
side: '@?' side: '@?'
}, },
require: { require: {
app: '^vnApp' layout: '^vnLayout'
} }
}); });

View File

@ -1,5 +1,8 @@
@import "variables"; @import "variables";
vn-side-menu {
display: block;
}
vn-side-menu > .menu { vn-side-menu > .menu {
display: block; display: block;
position: fixed; position: fixed;
@ -10,17 +13,17 @@ vn-side-menu > .menu {
background-color: $color-bg-panel; background-color: $color-bg-panel;
box-shadow: 0 .1em .2em $color-shadow; box-shadow: 0 .1em .2em $color-shadow;
overflow: auto; overflow: auto;
top: $topbar-height;
&.left { &.left {
left: 0; left: 0;
top: 0;
} }
&.right { &.right {
right: 0; right: 0;
top: $topbar-height;
} }
@media screen and (max-width: $mobile-width) { @media screen and (max-width: $mobile-width) {
top: 0;
transition: transform 200ms ease-out; transition: transform 200ms ease-out;
z-index: 15; z-index: 15;
@ -28,6 +31,7 @@ vn-side-menu > .menu {
transform: translateZ(0) translateX(-$menu-width); transform: translateZ(0) translateX(-$menu-width);
} }
&.right { &.right {
top: 0;
transform: translateZ(0) translateX($menu-width); transform: translateZ(0) translateX($menu-width);
} }
&.shown { &.shown {

View File

@ -59,4 +59,5 @@
.vn-popup .summary { .vn-popup .summary {
border: none; border: none;
box-shadow: none; box-shadow: none;
margin: 0;
} }

View File

@ -2,15 +2,8 @@
vn-topbar { vn-topbar {
display: flex; display: flex;
align-items: center;
color: $color-font-dark; color: $color-font-dark;
box-sizing: border-box; box-sizing: border-box;
background-color: $color-header; background-color: $color-header;
align-items: center;
& > header {
height: inherit;
width: inherit;
display: flex;
align-items: center;
}
} }

View File

@ -1,7 +1 @@
import ngModule from '../../module';
import './style.scss'; import './style.scss';
ngModule.component('vnTopbar', {
template: require('./topbar.html'),
transclude: true
});

View File

@ -11,7 +11,7 @@
font-size: 60px; font-size: 60px;
border-radius: 50%; border-radius: 50%;
color: $color-font-dark; color: $color-font-dark;
background: $color-secondary; background: $color-marginal;
padding: .1em; padding: .1em;
} }
& > div { & > div {

View File

@ -62,9 +62,10 @@ export function config($translatePartialLoaderProvider, $httpProvider, $compileP
$translatePartialLoaderProvider.addPart(appName); $translatePartialLoaderProvider.addPart(appName);
$httpProvider.interceptors.push('vnInterceptor'); $httpProvider.interceptors.push('vnInterceptor');
// $compileProvider.debugInfoEnabled(false); $compileProvider
$compileProvider.commentDirectivesEnabled(false); .debugInfoEnabled(false)
$compileProvider.cssClassDirectivesEnabled(false); .commentDirectivesEnabled(false)
.cssClassDirectivesEnabled(false);
} }
ngModule.config(config); ngModule.config(config);

View File

@ -1,41 +1,23 @@
import ngModule from './module'; import ngModule from './module';
import getMainRoute from 'core/lib/get-main-route'; import getMainRoute from 'core/lib/get-main-route';
function loader(moduleName) {
load.$inject = ['vnModuleLoader'];
function load(moduleLoader) {
return moduleLoader.load(moduleName);
}
return load;
}
config.$inject = ['$stateProvider', '$urlRouterProvider']; config.$inject = ['$stateProvider', '$urlRouterProvider'];
function config($stateProvider, $urlRouterProvider) { function config($stateProvider, $urlRouterProvider) {
$urlRouterProvider.otherwise('/'); $urlRouterProvider.otherwise('/');
$stateProvider.state('home', { $stateProvider
url: '/', .state('login', {
template: '<vn-home></vn-home>', url: '/login?continue',
description: 'Home' description: 'Login',
}); views: {
$stateProvider.state('login', { login: {template: '<vn-login></vn-login>'}
url: '/login?continue', }
template: '<vn-login></vn-login>', })
description: 'Login' .state('home', {
}); url: '/',
description: 'Home',
function getParams(route) { template: '<vn-home></vn-home>'
let params = '';
let temporalParams = [];
if (!route.params)
return params;
Object.keys(route.params).forEach(key => {
temporalParams.push(`${key} = "${route.params[key]}"`);
}); });
return temporalParams.join(' ');
}
for (let file in window.routes) { for (let file in window.routes) {
let routeFile = window.routes[file]; let routeFile = window.routes[file];
@ -68,5 +50,26 @@ function config($stateProvider, $urlRouterProvider) {
$stateProvider.state(route.state, configRoute); $stateProvider.state(route.state, configRoute);
} }
} }
function getParams(route) {
let params = '';
let temporalParams = [];
if (!route.params)
return params;
Object.keys(route.params).forEach(key => {
temporalParams.push(`${key} = "${route.params[key]}"`);
});
return temporalParams.join(' ');
}
function loader(moduleName) {
load.$inject = ['vnModuleLoader'];
function load(moduleLoader) {
return moduleLoader.load(moduleName);
}
return load;
}
} }
ngModule.config(config); ngModule.config(config);

View File

@ -123,25 +123,14 @@ html [scrollable] {
font-size: 0.7em font-size: 0.7em
} }
} }
[compact], .compact {
margin-left: auto;
margin-right: auto;
max-width: $width-md;
}
vn-empty-rows {
display: block;
text-align: center;
padding: 1.5em;
box-sizing: border-box;
}
/* XXX: Deprecated, use classes with text prefix */ /* XXX: Deprecated, use classes with text prefix */
[color-main] { [color-main] {
color: $color-main; color: $color-main;
} }
[color-secondary] { [color-marginal] {
color: $color-secondary; color: $color-marginal;
} }
[uppercase], .uppercase { [uppercase], .uppercase {
text-transform: uppercase; text-transform: uppercase;

View File

@ -84,14 +84,17 @@
</vn-horizontal> </vn-horizontal>
</vn-card> </vn-card>
<vn-button-bar> <vn-button-bar>
<vn-submit
label="Save"
vn-acl="deliveryBoss">
</vn-submit>
<vn-button <vn-button
label="Undo changes" label="Undo changes"
ng-if="watcher.dataChanged()" ng-if="watcher.dataChanged()"
ng-click="watcher.loadOriginalData()"> ng-click="watcher.loadOriginalData()">
</vn-button> </vn-button>
</vn-button-bar> </vn-button-bar>
<vn-submit
icon="save"
vn-acl="deliveryBoss"
vn-tooltip="Save"
class="round"
fixed-bottom-right>
</vn-submit>
</form> </form>

View File

@ -1,5 +1,5 @@
<vn-side-menu side="left"> <vn-portal slot="menu">
<vn-zone-descriptor zone="$ctrl.zone"></vn-zone-descriptor> <vn-zone-descriptor zone="$ctrl.zone"></vn-zone-descriptor>
<vn-left-menu></vn-left-menu> <vn-left-menu source="card"></vn-left-menu>
</vn-side-menu> </vn-portal>
<div ui-view></div> <ui-view></ui-view>

View File

@ -1,38 +1,21 @@
import ngModule from '../module'; import ngModule from '../module';
import ModuleCard from 'salix/components/module-card';
class Controller { class Controller extends ModuleCard {
constructor($http, $stateParams) { reload() {
this.$http = $http;
this.$stateParams = $stateParams;
}
$onInit() {
this.getCard();
}
getCard() {
let filter = { let filter = {
include: { include: {
relation: 'agencyMode', relation: 'agencyMode',
scope: {fields: ['name']} scope: {fields: ['name']}
} }
}; };
let json = encodeURIComponent(JSON.stringify(filter));
let query = `Zones/${this.$stateParams.id}?filter=${json}`;
this.$http.get(query).then(res => {
if (res.data)
this.zone = res.data;
});
}
reload() { this.$http.get(`Zones/${this.$params.id}`, {filter})
this.getCard(); .then(res => this.zone = res.data);
} }
} }
Controller.$inject = ['$http', '$stateParams']; ngModule.vnComponent('vnZoneCard', {
ngModule.component('vnZoneCard', {
template: require('./index.html'), template: require('./index.html'),
controller: Controller controller: Controller
}); });

View File

@ -1,35 +1,26 @@
import './index.js'; import './index.js';
describe('Agency Component vnZoneCard', () => { describe('Agency Component vnZoneCard', () => {
let $scope;
let controller; let controller;
let $httpBackend; let $httpBackend;
let $stateParams; let data = {id: 1, name: 'fooName'};
beforeEach(ngModule('agency')); beforeEach(ngModule('agency'));
beforeEach(angular.mock.inject(($componentController, $rootScope, _$httpBackend_) => { beforeEach(angular.mock.inject(($componentController, _$httpBackend_, $stateParams) => {
$httpBackend = _$httpBackend_; $httpBackend = _$httpBackend_;
$scope = $rootScope.$new();
$stateParams = {id: 1}; let $element = angular.element('<div></div>');
controller = $componentController('vnZoneCard', {$scope, $stateParams}); controller = $componentController('vnZoneCard', {$element});
$stateParams.id = data.id;
$httpBackend.whenRoute('GET', 'Zones/:id').respond(data);
})); }));
describe('getCard()', () => { it('should request data and set it on the controller', () => {
it(`should make a query and define zone property`, () => { controller.reload();
let filter = { $httpBackend.flush();
include: {
relation: 'agencyMode',
scope: {fields: ['name']}
}
};
let json = encodeURIComponent(JSON.stringify(filter));
$httpBackend.expectGET(`Zones/1?filter=${json}`).respond({id: 1});
controller.getCard();
$httpBackend.flush();
expect(controller.zone).toEqual({id: 1}); expect(controller.zone).toEqual(data);
});
}); });
}); });

View File

@ -5,79 +5,82 @@
form="form" form="form"
save="post"> save="post">
</vn-watcher> </vn-watcher>
<div class="content-block"> <form
<form name="form" vn-http-submit="$ctrl.onSubmit()" compact> name="form"
<vn-card class="vn-pa-lg"> vn-http-submit="$ctrl.onSubmit()"
<vn-horizontal> class="vn-w-md">
<vn-textfield <vn-card class="vn-pa-lg">
vn-one <vn-horizontal>
vn-focus <vn-textfield
label="Name" vn-one
ng-model="$ctrl.zone.name" vn-focus
rule> label="Name"
</vn-textfield> ng-model="$ctrl.zone.name"
</vn-horizontal> rule>
<vn-horizontal> </vn-textfield>
<vn-autocomplete </vn-horizontal>
vn-one <vn-horizontal>
ng-model="$ctrl.zone.warehouseFk" <vn-autocomplete
url="Warehouses" vn-one
show-field="name" ng-model="$ctrl.zone.warehouseFk"
value-field="id" url="Warehouses"
label="Warehouse" show-field="name"
rule> value-field="id"
</vn-autocomplete> label="Warehouse"
<vn-autocomplete rule>
vn-one </vn-autocomplete>
ng-model="$ctrl.zone.agencyModeFk" <vn-autocomplete
url="AgencyModes/isActive" vn-one
show-field="name" ng-model="$ctrl.zone.agencyModeFk"
value-field="id" url="AgencyModes/isActive"
label="Agency" show-field="name"
rule> value-field="id"
</vn-autocomplete> label="Agency"
</vn-horizontal> rule>
<vn-horizontal> </vn-autocomplete>
<vn-input-number </vn-horizontal>
vn-two <vn-horizontal>
label="Traveling days" <vn-input-number
ng-model="$ctrl.zone.travelingDays" vn-two
min="0" label="Traveling days"
step="1" ng-model="$ctrl.zone.travelingDays"
rule> min="0"
</vn-input-number> step="1"
<vn-input-time rule>
vn-two </vn-input-number>
label="Estimated hour (ETD)" <vn-input-time
ng-model="$ctrl.zone.hour" vn-two
rule> label="Estimated hour (ETD)"
</vn-input-time> ng-model="$ctrl.zone.hour"
</vn-horizontal> rule>
<vn-horizontal> </vn-input-time>
<vn-input-number </vn-horizontal>
vn-one <vn-horizontal>
label="Price" <vn-input-number
model="$ctrl.zone.price" vn-one
min="0" label="Price"
step="0.01" ng-model="$ctrl.zone.price"
rule> min="0"
</vn-input-number> step="0.01"
<vn-input-number rule>
vn-one </vn-input-number>
label="Bonus" <vn-input-number
model="$ctrl.zone.bonus" vn-one
min="0" label="Bonus"
step="0.01" ng-model="$ctrl.zone.bonus"
rule> min="0"
</vn-input-number> step="0.01"
</vn-horizontal> rule>
<vn-horizontal> </vn-input-number>
<vn-check ng-model="$ctrl.zone.isVolumetric" label="Volumetric"></vn-check> </vn-horizontal>
</vn-horizontal> <vn-horizontal>
</vn-card> <vn-check ng-model="$ctrl.zone.isVolumetric" label="Volumetric"></vn-check>
<vn-button-bar> </vn-horizontal>
<vn-submit label="Create"></vn-submit> </vn-card>
<vn-button ui-sref="zone.index" label="Cancel"></vn-button> <vn-submit
</vn-button-bar> icon="check"
</form> vn-tooltip="Create"
</div> class="round"
fixed-bottom-right>
</vn-submit>
</form>

View File

@ -32,9 +32,12 @@
</vn-textfield> </vn-textfield>
</vn-horizontal> </vn-horizontal>
</vn-card> </vn-card>
<vn-button-bar> <vn-submit
<vn-submit label="Query"></vn-submit> icon="search"
</vn-button-bar> vn-tooltip="Query"
class="round"
fixed-bottom-right>
</vn-submit>
</form> </form>
<vn-zone-calendar <vn-zone-calendar
data="data" data="data"

View File

@ -1,11 +1,9 @@
<div class="main-with-right-menu"> <vn-zone-calendar
<vn-zone-calendar id="calendar"
id="calendar" data="data"
data="data" on-selection="$ctrl.onSelection($days, $type, $weekday, $data)"
on-selection="$ctrl.onSelection($days, $type, $weekday, $data)" class="vn-w-md">
class="vn-w-md"> </vn-zone-calendar>
</vn-zone-calendar>
</div>
<vn-side-menu side="right"> <vn-side-menu side="right">
<div class="vn-pa-md"> <div class="vn-pa-md">
<h6 <h6
@ -35,59 +33,56 @@
</h6> </h6>
<vn-data-viewer <vn-data-viewer
data="data.events" data="data.events"
is-loading="!data.events" is-loading="!data.events">
class="vn-w-sm"> <div class="vn-list separated">
<div class="vn-list">
<a <a
ng-repeat="row in data.events" ng-repeat="row in data.events"
translate-attr="{title: 'Edit'}" translate-attr="{title: 'Edit'}"
ng-click="$ctrl.onEditClick(row, $event)" ng-click="$ctrl.onEditClick(row, $event)"
class="vn-list-item"> class="vn-item">
<vn-horizontal> <vn-item-section>
<vn-one> <div
<div ng-if="::row.from && !row.to"
ng-if="::row.from && !row.to" class="vn-mb-sm">
class="vn-mb-sm"> {{::row.from | date:'dd/MM/yy'}}
{{::row.from | date:'dd/MM/yy'}} </div>
</div> <div
<div ng-if="::!row.from || row.to"
ng-if="::!row.from || row.to" class="vn-mb-sm ellipsize">
class="vn-mb-sm ellipsize"> <span ng-if="row.to">
<span ng-if="row.to"> {{::row.from | date:'dd/MM/yy'}} - {{::row.to | date:'dd/MM/yy'}}
{{::row.from | date:'dd/MM/yy'}} - {{::row.to | date:'dd/MM/yy'}} </span>
</span> <span ng-if="!row.to" translate>
<span ng-if="!row.to" translate> Indefinitely
Indefinitely </span>
</span> <span ng-if="row.weekDays">
<span ng-if="row.weekDays"> ({{::$ctrl.formatWdays(row.weekDays)}})
({{::$ctrl.formatWdays(row.weekDays)}}) </span>
</span> </div>
</div> <vn-label-value
<vn-label-value label="Closing"
label="Closing" value="{{::row.hour | date:'hh:mm'}}">
value="{{::row.hour | date:'hh:mm'}}"> </vn-label-value>
</vn-label-value> <vn-label-value
<vn-label-value label="Traveling days"
label="Traveling days" value="{{::row.travelingDays}}">
value="{{::row.travelingDays}}"> </vn-label-value>
</vn-label-value> <vn-label-value
<vn-label-value label="Price"
label="Price" value="{{::row.price | currency:'EUR':2}}">
value="{{::row.price | currency:'EUR':2}}"> </vn-label-value>
</vn-label-value> <vn-label-value
<vn-label-value label="Bonus"
label="Bonus" value="{{::row.bonus | currency:'EUR':2}}">
value="{{::row.bonus | currency:'EUR':2}}"> </vn-label-value>
</vn-label-value> </vn-item-section>
</vn-one> <vn-item-section side>
<vn-horizontal class="buttons"> <vn-icon-button
<vn-icon-button icon="delete"
icon="delete" translate-attr="{title: 'Delete'}"
translate-attr="{title: 'Delete'}" ng-click="$ctrl.onDeleteClick(row.id, $event)">
ng-click="$ctrl.onDeleteClick(row.id, $event)"> </vn-icon-button>
</vn-icon-button> </vn-item-section>
</vn-horizontal>
</vn-horizontal>
</a> </a>
</div> </div>
</vn-data-viewer> </vn-data-viewer>

View File

@ -6,63 +6,56 @@
data="zones" data="zones"
auto-load="true"> auto-load="true">
</vn-crud-model> </vn-crud-model>
<div> <vn-auto-search
<vn-card class="vn-w-sm vn-pa-md"> model="model"
<vn-searchbar expr-builder="$ctrl.exprBuilder(param, value)">
panel="vn-zone-search-panel" </vn-auto-search>
model="model" <vn-data-viewer
expr-builder="$ctrl.exprBuilder(param, value)" model="model"
info="Search zone by id or name" class="vn-w-md vn-mb-xl">
vn-focus> <vn-card>
</vn-searchbar> <vn-table model="model">
<vn-thead>
<vn-tr>
<vn-th field="id" number>Id</vn-th>
<vn-th field="name" expand>Name</vn-th>
<vn-th field="agencyModeFk">Agency</vn-th>
<vn-th field="hour" shrink>Closing</vn-th>
<vn-th field="price" number>Price</vn-th>
<vn-th shrink></vn-th>
</vn-tr>
</vn-thead>
<vn-tbody>
<vn-tr
ng-repeat="zone in zones"
ui-sref="zone.card.location({id: zone.id})"
class="clickable search-result">
<vn-td number>{{::zone.id}}</vn-td>
<vn-td expand>{{::zone.name}}</vn-td>
<vn-td>{{::zone.agencyMode.name}}</vn-td>
<vn-td shrink>{{::zone.hour | date: 'HH:mm'}}</vn-td>
<vn-td number>{{::zone.price | currency: 'EUR':2}}</vn-td>
<vn-td shrink>
<vn-horizontal class="buttons">
<vn-icon-button
ng-click="$ctrl.clone($event, zone)"
vn-tooltip="Clone"
icon="icon-clone"
vn-acl="deliveryBoss"
vn-acl-action="remove">
</vn-icon-button>
<vn-icon-button
ng-click="$ctrl.preview($event, zone)"
vn-tooltip="Preview"
icon="desktop_windows">
</vn-icon-button>
</vn-horizontal>
</vn-td>
</vn-tr>
</vn-tbody>
</vn-table>
</vn-card> </vn-card>
<vn-data-viewer </vn-data-viewer>
model="model"
class="vn-w-md vn-mt-md vn-mb-xl">
<vn-card>
<vn-table model="model">
<vn-thead>
<vn-tr>
<vn-th field="id" number>Id</vn-th>
<vn-th field="name" expand>Name</vn-th>
<vn-th field="agencyModeFk">Agency</vn-th>
<vn-th field="hour" shrink>Closing</vn-th>
<vn-th field="price" number>Price</vn-th>
<vn-th shrink></vn-th>
</vn-tr>
</vn-thead>
<vn-tbody>
<vn-tr
ng-repeat="zone in zones"
ui-sref="zone.card.location({id: zone.id})"
class="clickable searchResult">
<vn-td number>{{::zone.id}}</vn-td>
<vn-td expand>{{::zone.name}}</vn-td>
<vn-td>{{::zone.agencyMode.name}}</vn-td>
<vn-td shrink>{{::zone.hour | date: 'HH:mm'}}</vn-td>
<vn-td number>{{::zone.price | currency: 'EUR':2}}</vn-td>
<vn-td shrink>
<vn-horizontal class="buttons">
<vn-icon-button
ng-click="$ctrl.clone($event, zone)"
vn-tooltip="Clone"
icon="icon-clone"
vn-acl="deliveryBoss"
vn-acl-action="remove">
</vn-icon-button>
<vn-icon-button
ng-click="$ctrl.preview($event, zone)"
vn-tooltip="Preview"
icon="desktop_windows">
</vn-icon-button>
</vn-horizontal>
</vn-td>
</vn-tr>
</vn-tbody>
</vn-table>
</vn-card>
</vn-data-viewer>
</div>
<vn-popup vn-id="summary"> <vn-popup vn-id="summary">
<vn-zone-summary zone="$ctrl.selectedZone"></vn-zone-summary> <vn-zone-summary zone="$ctrl.selectedZone"></vn-zone-summary>
</vn-popup> </vn-popup>

View File

@ -3,25 +3,26 @@
url="Zones/{{$ctrl.$params.id}}/getLeaves" url="Zones/{{$ctrl.$params.id}}/getLeaves"
filter="::$ctrl.filter"> filter="::$ctrl.filter">
</vn-crud-model> </vn-crud-model>
<vn-portal slot="topbar">
<vn-searchbar></vn-searchbar>
</vn-portal>
<vn-auto-search
on-search="$ctrl.onSearch($params)">
</vn-auto-search>
<div class="vn-w-md"> <div class="vn-w-md">
<vn-card class="vn-pa-md"> <vn-card class="vn-pa-lg">
<vn-searchbar <vn-treeview
auto-load="false" vn-id="treeview"
on-search="$ctrl.onSearch($params)" root-label="Locations"
vn-focus> fetch-func="$ctrl.onFetch($item)"
</vn-searchbar> sort-func="$ctrl.onSort($a, $b)">
</vn-card> <vn-check
<vn-card class="vn-pa-lg vn-mt-md"> ng-model="item.selected"
<vn-treeview vn-id="treeview" root-label="Locations" on-change="$ctrl.onSelection(value, item)"
fetch-func="$ctrl.onFetch($item)" triple-state="true"
sort-func="$ctrl.onSort($a, $b)"> ng-click="$event.preventDefault()"
<vn-check label="{{::item.name}}">
ng-model="item.selected" </vn-check>
on-change="$ctrl.onSelection(value, item)" </vn-treeview>
triple-state="true"
ng-click="$event.preventDefault()"
label="{{::item.name}}">
</vn-check>
</vn-treeview>
</vn-card> </vn-card>
</div> </div>

View File

@ -3,6 +3,10 @@ import Component from 'core/lib/component';
import './style.scss'; import './style.scss';
class Controller extends Component { class Controller extends Component {
$postLink() {
this.onSearch();
}
onSearch(params) { onSearch(params) {
this.$.model.applyFilter({}, params).then(() => { this.$.model.applyFilter({}, params).then(() => {
const data = this.$.model.data; const data = this.$.model.data;
@ -12,9 +16,8 @@ class Controller extends Component {
onFetch(item) { onFetch(item) {
const params = item ? {parentId: item.id} : null; const params = item ? {parentId: item.id} : null;
return this.$.model.applyFilter({}, params).then(() => { return this.$.model.applyFilter({}, params)
return this.$.model.data; .then(() => this.$.model.data);
});
} }
onSort(a, b) { onSort(a, b) {

View File

@ -2,13 +2,13 @@
vn-treeview-child { vn-treeview-child {
.content > .vn-check:not(.indeterminate) { .content > .vn-check:not(.indeterminate) {
color: $color-main; color: $color-button;
& > .btn { & > .btn {
border-color: $color-main; border-color: $color-button;
} }
} }
.content > .vn-check.checked { .content > .vn-check.checked {
color: $color-main; color: $color-button;
} }
} }

View File

@ -1,14 +1,11 @@
<vn-main-block> <vn-portal slot="topbar">
<vn-side-menu side="left"> <vn-searchbar
<ul class="menu"> search-state="zone.index"
<li> panel="vn-zone-search-panel"
<a translate ui-sref="zone.index">Zones</a> info="Search zone by id or name">
</li> </vn-searchbar>
<li> </vn-portal>
<a translate ui-sref="zone.deliveryDays">Delivery days</a> <vn-portal slot="menu">
</li> <vn-left-menu></vn-left-menu>
</ul> </vn-portal>
</vn-side-menu> <ui-view></ui-view>
<div class="content-block" ui-view></div>
</vn-main-block>

View File

@ -1,6 +1,9 @@
import ngModule from '../module'; import ngModule from '../module';
import './style.scss'; import ModuleMain from 'salix/components/module-main';
ngModule.component('vnZone', { export default class Zone extends ModuleMain {}
ngModule.vnComponent('vnZone', {
controller: Zone,
template: require('./index.html') template: require('./index.html')
}); });

View File

@ -1,18 +0,0 @@
@import "effects";
vn-zone {
ul.menu {
list-style-type: none;
padding: 0;
padding-top: $spacing-md;
margin: 0;
font-size: inherit;
& > li > a {
@extend %clickable;
display: block;
color: inherit;
padding: .6em 2em;
}
}
}

Some files were not shown because too many files have changed in this diff Show More