2
0
mirror of https://github.com/frappe/books.git synced 2025-02-02 03:58:26 +00:00

Merge pull request #29 from netchampfaris/electronify

Electronify
This commit is contained in:
Rushabh Mehta 2018-04-02 12:38:11 +05:30 committed by GitHub
commit e4dd0a9954
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 5065 additions and 932 deletions

3
.gitignore vendored
View File

@ -4,4 +4,5 @@ Thumbs.db
*test.db
*.log
.cache
/temp
/temp
dist

68
electron/client.js Normal file
View File

@ -0,0 +1,68 @@
const path = require('path');
const electron = require('frappejs/client/electron');
const { writeFile } = require('frappejs/server/utils');
const appClient = require('../client');
const SetupWizard = require('../setup');
(async () => {
const configFilePath = path.join(require('os').homedir(), '.config', 'frappe-accounting', 'settings.json');
let settings, dbPath;
try {
settings = require(configFilePath);
} catch(e) {
settings = {}
}
if (settings.dbPath) {
dbPath = settings.dbPath;
electron.start({
dbPath,
models: require('../models')
}).then(() => {
appClient.start();
});
} else {
const setup = new SetupWizard();
window.setup = setup;
const values = await setup.start();
const {
companyName,
file,
country,
name,
email,
abbreviation,
bankName
} = values;
dbPath = path.join(file[0].path, companyName + '.db');
electron.start({
dbPath,
models: require('../models')
}).then(async () => {
await writeFile(configFilePath, JSON.stringify({
directory: path.dirname(dbPath),
dbPath: dbPath
}));
const doc = await frappe.getDoc('AccountingSettings');
await doc.set('companyName', companyName);
await doc.set('file', dbPath);
await doc.set('country', country);
await doc.set('fullname', name);
await doc.set('email', email);
await doc.set('abbreviation', abbreviation);
await doc.set('bankName', bankName);
await doc.update();
appClient.start();
})
}
})();

12
electron/index.html Normal file
View File

@ -0,0 +1,12 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<link href="../www/dist/css/style.css" rel="stylesheet">
</head>
<body>
<script src="./client.js"></script>
</body>
</html>

2710
fixtures/countryInfo.json Normal file

File diff suppressed because it is too large Load Diff

60
main.js Normal file
View File

@ -0,0 +1,60 @@
const electron = require('electron')
// Module to control application life.
const app = electron.app
// Module to create native browser window.
const BrowserWindow = electron.BrowserWindow
const path = require('path')
const url = require('url')
// Keep a global reference of the window object, if you don't, the window will
// be closed automatically when the JavaScript object is garbage collected.
let mainWindow
function createWindow () {
// Create the browser window.
mainWindow = new BrowserWindow({width: 800, height: 600})
// and load the index.html of the app.
mainWindow.loadURL(url.format({
pathname: path.join(__dirname, 'electron/index.html'),
protocol: 'file:',
slashes: true
}))
// Open the DevTools.
// mainWindow.webContents.openDevTools()
// Emitted when the window is closed.
mainWindow.on('closed', function () {
// Dereference the window object, usually you would store windows
// in an array if your app supports multi windows, this is the time
// when you should delete the corresponding element.
mainWindow = null
})
}
// This method will be called when Electron has finished
// initialization and is ready to create browser windows.
// Some APIs can only be used after this event occurs.
app.on('ready', createWindow)
// Quit when all windows are closed.
app.on('window-all-closed', function () {
// On OS X it is common for applications and their menu bar
// to stay active until the user quits explicitly with Cmd + Q
if (process.platform !== 'darwin') {
app.quit()
}
})
app.on('activate', function () {
// On OS X it's common to re-create a window in the app when the
// dock icon is clicked and there are no other windows open.
if (mainWindow === null) {
createWindow()
}
})
// In this file you can include the rest of your app's specific main process
// code. You can also put them in separate files and require them here.

View File

@ -1,3 +1,5 @@
const countryList = Object.keys(require('../../../fixtures/countryInfo.json')).sort();
module.exports = {
name: "AccountingSettings",
label: "AccountingSettings",
@ -9,15 +11,60 @@ module.exports = {
keywordFields: [],
fields: [
{
fieldname: "Company Name",
label: "companyName",
label: "Company Name",
fieldname: "companyName",
fieldtype: "Data",
required: 1
},
{
fieldname: "Writeoff Account",
label: "writeOffAccount",
label: "Writeoff Account",
fieldname: "writeOffAccount",
fieldtype: "Account"
},
{
"fieldname": "file",
"label": "File",
"fieldtype": "Data",
"required": 1,
"directory": 1
},
{
"fieldname": "country",
"label": "Country",
"fieldtype": "Autocomplete",
"required": 1,
getList: () => countryList
},
{
"fieldname": "fullname",
"label": "Name",
"fieldtype": "Data",
"required": 1
},
{
"fieldname": "email",
"label": "Email",
"fieldtype": "Data",
"required": 1
},
{
"fieldname": "abbreviation",
"label": "Abbreviation",
"fieldtype": "Data",
"required": 1
},
{
"fieldname": "bankName",
"label": "Bank Name",
"fieldtype": "Data",
"required": 1
}
]

View File

@ -1,6 +1,7 @@
module.exports = {
models: {
Account: require('./doctype/Account/Account.js'),
AccountingSettings: require('./doctype/AccountingSettings/AccountingSettings'),
AccountingLedgerEntry: require('./doctype/AccountingLedgerEntry/AccountingLedgerEntry.js'),
Party: require('./doctype/Party/Party.js'),

View File

@ -1,15 +1,31 @@
{
"name": "frappe-accounting",
"description": "Simple Accounting app for everyone",
"productName": "Frappe Accounting",
"version": "1.0.0",
"main": "start.js",
"author": {
"name": "Frappe Technologies Pvt. Ltd.",
"email": "hello@frappe.io"
},
"build": {
"appId": "io.frappe.accounting"
},
"main": "main.js",
"license": "MIT",
"scripts": {
"test": "mocha tests",
"start": "nodemon start.js"
"start": "nodemon server.js",
"watch": "rollup -c --watch",
"electron": "electron main.js",
"electron-pack": "electron-packager . --overwrite",
"postinstall": "electron-builder install-app-deps"
},
"dependencies": {
"frappejs": "link:../frappejs"
"frappejs": "../frappejs"
},
"devDependencies": {
"electron": "1.8.4",
"electron-builder": "^20.6.2",
"electron-packager": "^11.2.0"
}
}

View File

@ -1,7 +1,7 @@
const server = require('frappejs/server');
const frappe = require('frappejs');
const GeneralLedger = require('../reports/generalLedger/GeneralLedger')
const naming = require('frappejs/model/naming')
const GeneralLedger = require('../reports/generalLedger/GeneralLedger');
const naming = require('frappejs/model/naming');
module.exports = {
async start() {

View File

@ -1,55 +1,86 @@
module.exports = [
{
fields: [
{
"fieldname": "country",
"label": "Country",
"fieldtype": "Data",
"required": 1
}
],
title: 'Select Country'
},
const countryList = Object.keys(require('../fixtures/countryInfo.json')).sort();
{
fields: [
{
"fieldname": "name",
"label": "Name",
"fieldtype": "Data",
"required": 1
},
{
"fieldname": "email",
"label": "Email",
"fieldtype": "Data",
"required": 1
}
],
title: 'Add a Profile'
},
module.exports = {
fields: [
{
fields: [
{
"fieldname": "companyName",
"label": "Company Name",
"fieldtype": "Data",
"required": 1
},
{
"fieldname": "abbreviation",
"label": "Abbreviation",
"fieldtype": "Data",
"required": 1
},
{
"fieldname": "bankName",
"label": "Bank Name",
"fieldtype": "Data",
"required": 1
}
],
title: 'Add your Company'
}
]
{
"fieldname": "file",
"label": "File",
"fieldtype": "File",
"required": 1,
"directory": 1
},
{
"fieldname": "country",
"label": "Country",
"fieldtype": "Autocomplete",
"required": 1,
getList: () => countryList
},
{
"fieldname": "name",
"label": "Name",
"fieldtype": "Data",
"required": 1
},
{
"fieldname": "email",
"label": "Email",
"fieldtype": "Data",
"required": 1
},
{
"fieldname": "companyName",
"label": "Company Name",
"fieldtype": "Data",
"required": 1
},
{
"fieldname": "abbreviation",
"label": "Abbreviation",
"fieldtype": "Data",
"required": 1
},
{
"fieldname": "bankName",
"label": "Bank Name",
"fieldtype": "Data",
"required": 1
}
],
layout: [
{
title: 'Select File location',
fields: ['file']
},
{
title: 'Select Country',
fields: ['country']
},
{
title: 'Add a Profile',
fields: ['name', 'email']
},
{
title: 'Add your Company',
columns: [
{
fields: ['companyName', 'bankName']
},
{
fields: ['abbreviation']
},
]
}
]
}

View File

@ -3,20 +3,44 @@ const utils = require('frappejs/client/ui/utils');
const slideConfigs = require('./config');
const Tree = require('frappejs/client/ui/tree');
const FormLayout = require('frappejs/client/view/formLayout');
const Observable = require('frappejs/utils/observable');
module.exports = class SetupWizard {
constructor({postSetup = () => {}}) {
this.slideList = [];
constructor() {
this.slideCount = slideConfigs.layout.length;
this.indicatorList = [];
this.footerLinks = {};
this.currentIndex = 0;
this.data = {};
this.doc = new Observable();
this.promise = new Promise(resolve => {
this.onComplete = resolve;
});
this.footerButtons = [
{
label: 'Prev', name: 'prev',
action: this.prevSlide.bind(this),
condition: index => index !== 0
},
{
label: 'Next', name: 'next',
action: this.nextSlide.bind(this),
condition: index => index !== this.slideCount - 1
},
{
label: 'Complete', name: 'complete',
action: this.onComplete.bind(this, this.doc),
condition: index => index === this.slideCount - 1
}
];
this.postSetup = postSetup;
this.make();
}
this.showSlide(this.currentIndex);
async start() {
this.showSlide(0);
return this.promise;
}
make() {
@ -25,60 +49,39 @@ module.exports = class SetupWizard {
this.$indicators = frappe.ui.add('div', 'indicators vertical-margin align-center', this.container);
this.makeSlides();
this.makeLinks();
this.makeButtons();
}
makeSlides() {
slideConfigs.forEach(config => {
this.formLayout = new FormLayout(config);
this.slideList.push(this.formLayout);
let form = this.formLayout.form;
this.container.appendChild(form);
let title = frappe.ui.create('h3', {
className: 'text-extra-muted',
innerHTML: config.title
})
form.insertBefore(title, form.firstChild);
this.formLayout = new FormLayout(Object.assign(slideConfigs, {
doc: this.doc
}));
this.container.appendChild(this.formLayout.form);
slideConfigs.layout.forEach(() => {
// indicator for each section
let indicator = frappe.ui.create('span', {
inside: this.$indicators,
className: 'indicator gray'
})
});
this.indicatorList.push(indicator);
});
}
makeLinks() {
makeButtons() {
this.linkArea = frappe.ui.add('div', 'setup-link-area align-right', this.container);
// this.formLayout.on('change', () => {
// const show = this.doc._dirty && !this.doc.submitted;
// this.saveButton.classList.toggle('hide', !show);
// });
this.getFooterLinks().map(link => {
let $link = utils.addLink(link.label, this.linkArea, () => {
this.buildData();
link.action(this.data);
});
this.footerLinks[link.name] = $link;
})
}
buildData() {
this.data = {};
this.slideList.forEach(slide => {
Object.assign(this.data, slide.doc);
this.footerButtons.map(link => {
link.element = utils.addButton(link.label, this.linkArea, link.action);
});
}
showSlide(index) {
utils.activate(this.container, this.slideList[index].form, 'form-body', 'active');
this.slideList[index].controlList[0].input.blur();
this.currentIndex = index;
utils.activate(this.container, `.form-section:nth-child(${index + 1})`, '.form-section', 'active');
this.activateIndicator(index);
this.showFooterLinks(index);
this.currentIndex = index;
}
prevSlide() {
@ -90,46 +93,22 @@ module.exports = class SetupWizard {
}
activateIndicator(index) {
this.indicatorList.forEach(indicator => {indicator.classList.add('gray')});
this.indicatorList.forEach(indicator => indicator.classList.add('gray'));
let indicator = this.indicatorList[index];
utils.activate(this.$indicators, indicator, 'gray', 'blue', index);
utils.activate(this.$indicators, indicator, '.gray', 'blue', index);
frappe.ui.removeClass(indicator, 'gray');
indicator.classList.remove('gray');
}
showFooterLinks(index) {
let mat = [1, 1, 0]
if(index === 0) {
mat = [0, 1, 0];
} else if (index === this.slideList.length - 1) {
mat = [1, 0, 1];
}
this.showHideLinks(mat);
}
showHideLinks(matrix = [1, 1, 0]) {
let linkNames = this.getFooterLinks().map(link => link.name);
matrix.forEach((value, i) => {
const fn = value ? 'remove' : 'add';
this.footerLinks[linkNames[i]].classList[fn]('hide');
});
}
getFooterLinks() {
return [
{
label: 'Prev', name: 'prev',
action: this.prevSlide.bind(this)
},
{
label: 'Next', name: 'next',
action: this.nextSlide.bind(this)
},
{
label: 'Complete', name: 'complete',
action: this.postSetup.bind(this)
this.footerButtons.map(link => {
const show = link.condition(this.currentIndex);
if (show) {
link.element.classList.remove('hide');
} else {
link.element.classList.add('hide');
}
];
})
}
}

View File

@ -7207,6 +7207,10 @@ span.CodeMirror-selectedtext {
border-bottom: 1px solid #d1d8dd; }
.body-scrollable.row-highlight-all .data-table-row:not(.row-unhighlight) {
background-color: #f5f7fa; }
.body-scrollable .no-data td {
text-align: center;
padding: 8px;
padding: 0.5rem; }
.data-table-header {
position: absolute;
top: 0;
@ -7494,6 +7498,74 @@ mark {
margin: 0.25rem; }
.vertical-margin {
margin: 1rem 0px; }
.tree {
padding: 15px; }
.tree li {
list-style: none;
margin: 2px 0px; }
ul.tree-children {
padding-left: 20px; }
.tree-link {
cursor: pointer;
display: inline-block;
padding: 1px; }
.tree-link .node-parent {
color: #6c757d;
font-size: 14px;
width: 10px;
text-align: center; }
.tree-link .node-leaf {
color: #ced4da; }
.tree-link .node-parent, .tree-link .node-leaf {
margin-right: 5px;
margin-left: 2px;
margin-top: 3px; }
.tree-link.active svg {
color: #007bff; }
.tree-link.active a {
color: #6c757d; }
.tree-hover {
background-color: #e9ecef;
min-height: 20px;
border: 1px solid #6c757d; }
.tree-node-toolbar {
display: inline-block;
padding: 0px 5px;
margin-left: 15px;
margin-bottom: -4px;
margin-top: -8px; }
.tree.with-skeleton, .tree.with-skeleton .tree-node {
position: relative; }
.tree.with-skeleton.opened::before, .tree.with-skeleton:last-child::after, .tree.with-skeleton .tree-node.opened::before, .tree.with-skeleton .tree-node:last-child::after {
content: '';
position: absolute;
top: 12px;
left: 7px;
height: calc(100% - 23px);
width: 1px;
background: #ced4da;
z-index: -1; }
.tree.with-skeleton:last-child::after, .tree.with-skeleton .tree-node:last-child::after {
top: 11px;
left: -13px;
height: calc(100% - 15px);
width: 3px;
background: #fff; }
.tree.with-skeleton.opened > .tree-children > .tree-node > .tree-link::before, .tree.with-skeleton .tree-node.opened > .tree-children > .tree-node > .tree-link::before {
content: '';
position: absolute;
width: 18px;
height: 1px;
top: 10px;
left: -12px;
z-index: -1;
background: #ced4da; }
.tree.with-skeleton.opened::before {
left: 22px;
top: 33px;
height: calc(100% - 67px); }
.tree-link.active ~ .balance-area {
color: #6c757d !important; }
.setup-container {
margin: 40px auto;
padding: 20px 0px;
@ -7502,9 +7574,16 @@ mark {
border-radius: 4px; }
.setup-container h3 {
text-align: center; }
.setup-container .form-body {
.setup-container .form-section {
display: none; }
.setup-container .form-body.active {
.setup-container .form-section.active {
display: block; }
.setup-container .setup-link-area {
margin: 0.25rem 2rem; }
input[type=file] {
width: 0.1px;
height: 0.1px;
opacity: 0;
overflow: hidden;
position: absolute;
z-index: -1; }

1569
www/dist/js/bundle.js vendored

File diff suppressed because it is too large Load Diff

1146
yarn.lock

File diff suppressed because it is too large Load Diff