A desktop client for macOS, linux and windows. Based on Electron. Webpack is used as an app bundler. A stylesheet language is SCSS, template engine β PugJS.
- NodeJS
>=8.0.0
- Yarn
>=1.9.0
Install project dependencies:
yarn
+ install all peer dependencies from package.json
Development: An app bundle with source maps, watching files and without css extraction.
yarn build:dev
yarn start:dev # background
Production: A production bundle. All source codes are minified, css files are extracted, etc.
yarn build:prod
yarn start:prod # background
Packager: Bundle the whole app into 1 execution file (.app
, .exe
, etc.).
yarn packager:mac # macOS application
yarn packager:lin # linux application
yarn packager:win # windows application
Universal clean task:
yarn clean
Other tasks:
yarn clean:build # clean build folder
yarn clean:release # clean releases
yarn clean:logs # clean logs
Project uses eslint utility. Airbnb JavaScript Style Guide is used as a basic style guide, other styles are defined in .eslintrc.json
. Please don't use olded syntax of ajax requests (XMLHttpRequest), prototype declarations etc.
.
βββ bin # binary files
βββ build # bundled application
βββ electron # electron configuration
βββ logs # developmetn logs
βββ release # bundled releases
βββ src #sources
βΒ Β βββ config # app configuration files
βΒ Β βββ kernel # app functionality core
βΒ Β βΒ Β βββ modules # modules
βΒ Β βββ local # app content
βΒ Β βββ components # repeatable parts of code
βΒ Β βββ localization # json files with translations
βΒ Β βΒ Β βββ xx.json
βΒ Β βββ resources # global resources (may be used everywhere)
βΒ Β βββ routes
βΒ Β βΒ Β βββ page # route name (usually a part between ../xxx/.. in URL)
βΒ Β βΒ Β βββ js # page secondary js
βΒ Β βΒ Β βββ pug # page secondary markup
βΒ Β βΒ Β βββ scss # page secondary scss
βΒ Β βΒ Β βββ index.js # controller
βΒ Β βΒ Β βββ index.pug # view
βΒ Β βΒ Β βββ index.scss # styles
βΒ Β βββ styles # global styles
βΒ Β β βββ ui # ui elements
βΒ Β β βββ ** # other style groups
βΒ Β βββ components.js # requires components
βΒ Β βββ localizations.js # requires localizations
βΒ Β βββ routes.js # defines routes
βββ test # tests
βββ tmp # temporary user folder (ignored by git)
The application supports multilanguage interfaces.
./src/config/localization.config.json # settings (default language, etc.)
./src/kernel/Localization.class.js # main localization class
./src/local/localization/??.json # all language data files
./src/local/localizations.js # requires language data files
Each language should have its own .json file, which is automatically required in the localization module (localizations.js
). Each phrase has a namespace
and a key
. This pairs should be unique per language file.
All routes (pages) are accepted by patterns, which are formed with paths and params. Paths are static, params are variable and not empty. E.g. a pattern /package/:id
could accept routes /package/e564wi
or /package/3245rtfde454e
, but not /package/
.
A pattern may look like /path/path/:param/path/:param/path
. It means if we request /path/path/2/path/4/path
, the router finds our pattern, gets all parameters [2, 4]
and sends them to the page renderer process.
To prevent an unexpected redirects always type a full path begins with a slash /
, e.g.:
<a href="/route/page">Link</a>
Let's create a new route with a path /element/:element-type/:element-name/view
.
.routes
βββ element
Β Β βββ element-type
Β Β βββ element-name
Β Β Β βββ view
Β Β Β βββ js # secondary js (optional) - additional js files for the controller
Β Β Β βββ pug # secondary pug (optional) - additional pug files for the markup
Β Β Β βββ scss # secondary scss (optional) - additional mixins, etc.
Β Β Β βββ index.js # controller (required)
Β Β Β βββ index.pug # markup (optional) - required in the controller
Β Β Β βββ index.scss # styles (optional) - required in the controller
Then you need to declare a new route in ./src/local/routes.js
. A key is used as a regular expression to match a requested page, a value is a page controller's exported function which runs immediately.
module.exports = {
'/element/:element-type/:element-name/view': require('Routes/element/element-type/element-name/view')(),
};
The controller is a class extends the Page template (./src/kernel/Page.class.js
) wrapped into an object structure with its configuration (see other page as examples). A minimal controller code is:
const Page = require('Kernel/Page.class');
class Index extends Page {
constructor(args, config) {
super(args, config);
}
}
module.exports = (config = {}) => ({
config,
Page: Index,
});
To add a stylesheet require it explicitly:
require('./index.scss');
To define a template (instead of the default one) you need to create a index.pug
file and redefine the __render()
method:
const Page = require('Kernel/Page.class');
const template = require('./index.pug');
class Index extends Page {
constructor(args, config) {
super(args, config);
this._template = template;
}
__render() {
this._getRenderParent().innerHTML = this._template();
}
}
module.exports = (config = {}) => ({
config,
Page: Index,
});
The __render()
method is one of the auto run methods which are called in the specific order during a process of page loading.
class Index extends Page {
async __ajaxData() {
// Purpose: load all initial data from the server
}
async __render() {
// Purpose: render a markup
}
__handlers() {
// Purpose: add some js events
}
}
Within pages may exist elements (components) that are used more than once, but with different texts etc., e.g. buttons, tiles, even whole templates (e.g. error pages). They are defined in ./src/local/components/**
and auto-loaded into all page classes.
components.js
assumes the existence of index.pug
file(s), the path of their parent folders joined with .
will be used as a unique id of the component.
All index.scss
files are required recursively.
Example of newComponent
component:
.src/local/components/
βββ groupOfComponents
βββ newComponent
Β Β βββ images # images, required in scss files (optional)
Β Β βββ scss # styles, required in index.scss (optional)
Β Β βββ index.pug # template file (required)
Β Β βββ index.scss # scss file (optional)
Pages may use this component in their renderer process:
const partOfHTML = this.getComponent()['groupOfComponents.newComponent']({
_lang: this._getLocalization(),
_data: {
optionalVariable: 'value',
},
});
The result may be send as a variable to some template.
Modules (./src/kernel/modules
) are classes or interfaces that can be required within the whole app.
const module = require('Modules/name.module');
Library: Noty.
const Noty = require('Modules/notifications.module');
// Types: [alert, error, warning, info (information), success]
new Noty({
type: 'error',
text: 'Notification example!',
timeout: 1000,
}).show();
Library: winston, winston-daily-rotate-file
const log = require('Modules/logger.app.module');
log.error('Error message.');
log.warn('Warning message.');
log.info('Info message.');
log.trace('Trace message.');
log.debug('Debug message.');
Full description and examples you can find in ./src/kernel/modules/**.js
.
Mocha is used as an environment and chai as an assertion library. Tests have their own folder (./test
) and are divided into 2 groups: api test (aren't connected with the current implementation) and application test (unit tests, etc.).
yarn mocha:all # run all tests
yarn mocha:api # run only API tests
yarn mocha:application # run only app tests
There are 3 main places from which we can call an API request - Router, Page rendering process and Page itself.
The default structure of handling responses from the server is (CALL)β(API-MODULE)β(CONNECT)
where individual nodes are:
CONNECT
./src/kernel/modules/connect.module.js
- provides different types of
fetch
with user token and other necessary data (an interface of getJSON, postJSON, etc.) - handles global errors (without redirections)
API-MODULE
./src/kernel/modules/api/*.module.js
- provides an API interface for logical groups of request
- handles individual save/remove events (without redirects)
CALL
evodoc.getAPI().getModule().method({...options})
- a request in a
try-catch
block - the
catch
section handles all redirects (e.g. a redirect after unauthorised error) and individual behavior (e.g. a notification about wrong password)
An API call visualisation:
.Router
ββ(CALL)β(API-MODULE)β(CONNECT) (1)
ββ Page rendering
ββ(CALL)β(API-MODULE)β(CONNECT) (2)
.Page
ββ(CALL)β(API-MODULE)β(CONNECT) (3)
Detailed communication:
(1) Router call <- (return|throw) <- Module <- (return|throw) <- Connect
(2) Router <- (throw) <- Page rendering call <- (return|throw) <- Module <- (return|throw) <- Connect
(3) Page call <- (return|throw) <- Module <- (return|throw) <- Connect
1000 - No internet connection
// Modules
const connect = require('Modules/connect.module');
const errorAuth = require('Modules/api/auth.error');
try {
// Call
await evodoc
.getAPI()
.getAuth()
.signIn({...});
// Redirect
evodoc.getRouter().load("/");
} catch (e) {
// Error handling
if (e instanceof errorAuth.InvalidAuthDataError) {
new Noty({
text: 'Login or password is invalid.',
}).show();
return;
}
connect.processOtherErrors(e);
}