Upload
others
View
1
Download
0
Embed Size (px)
Citation preview
Alinex GUIGUI for Alinex Server
Alexander Schilling
Copyright © 2020 - 2021 <a href="https://alinex.de">Alexander Schilling</a>
Table of contents
51. General
51.1 Alinex GUI Client
111.1.1 Chapters
111.1.2 Support
121.2 Last Changes
121.2.1 Version 0.4.2 (08.11.2020)
121.2.2 Version 0.4.1 (14.10.2020)
121.2.3 Version 0.4.0 (12.10.2020)
121.2.4 Version 0.3.0 (31.07.2020)
121.2.5 Version 0.2.0 (14.06.2020)
131.2.6 Version 0.1.0 (05.05.2020)
141.3 Roadmap
141.3.1 Version 0.5.0
141.3.2 Version 1.0.0
151.4 Privacy statement
162. Usage
162.1 User Manual
172.2 Basic Layout
222.3 Authentication
232.4 Management of Access Control Settings
252.5 System Information
263. Technology
263.1 Used Technologies
273.2 Vue.js
273.2.1 Basics
283.3 Quasar Framework
283.3.1 Structure
283.3.2 Pages and Components
293.4 State Management
293.4.1 Store
303.4.2 State
303.4.3 Getters
313.4.4 Mutations
313.4.5 Actions
313.4.6 Modules
Table of contents
- 2/67 - Copyright © 2020 - 2021 <a href="https://alinex.de">Alexander Schilling</a>
333.5 Feathers Integration
333.5.1 Authentication
333.5.2 Setup Services
333.5.3 Store
363.5.4 Model
383.5.5 Components
414. Framework
414.1 Core Framework
414.1.1 Features
414.1.2 3rd Party Packages
424.2 Setup
424.2.1 Configuration
424.2.2 Builds
444.3 Layout
444.3.1 Icons
444.3.2 Dark Mode
444.3.3 Sass Variables
514.4 Internationalization
514.4.1 File Structure
514.4.2 Use of translations
534.4.3 Management
544.5 Special Components
544.5.1 Global
544.5.2 Dialogs
554.5.3 Module Organization
574.6 Object Create, Read, Update and Delete
574.6.1 Routing
574.6.2 Store
574.6.3 Pages
574.6.4 Security
574.6.5 Translations
584.7 Store
595. Development
595.1 Development
605.2 Parent Project
615.2.1 Setup
645.3 Customization
645.3.1 Logo
Table of contents
- 3/67 - Copyright © 2020 - 2021 <a href="https://alinex.de">Alexander Schilling</a>
645.3.2 Colors
655.4 Extending Modules
655.4.1 Routing
655.4.2 Pages
665.4.3 Menu
675.4.4 Dashboard
Table of contents
- 4/67 - Copyright © 2020 - 2021 <a href="https://alinex.de">Alexander Schilling</a>
1. General
1.1 Alinex GUI ClientThis is the client used for the Alinex Server. It's an interface to do access and work with the server
through a graphical interface. It should be a base for specialized tools for specific environments
which can be customized to it's needs.
It is included in the Alinex Server.
1. General
- 5/67 - Copyright © 2020 - 2021 <a href="https://alinex.de">Alexander Schilling</a>
1.1 Alinex GUI Client
- 6/67 - Copyright © 2020 - 2021 <a href="https://alinex.de">Alexander Schilling</a>
1.1 Alinex GUI Client
- 7/67 - Copyright © 2020 - 2021 <a href="https://alinex.de">Alexander Schilling</a>
1.1 Alinex GUI Client
- 8/67 - Copyright © 2020 - 2021 <a href="https://alinex.de">Alexander Schilling</a>
1.1 Alinex GUI Client
- 9/67 - Copyright © 2020 - 2021 <a href="https://alinex.de">Alexander Schilling</a>
See more screenshots under usage.
Big Features:
modern design, responsive
different web technologies (SPA, PWA, SSR)
mobile apps (Android, IOS)
desktop application (Windows, Mac, Linux)
multi lingual with dark mode
•
•
•
•
•
1.1 Alinex GUI Client
- 10/67 - Copyright © 2020 - 2021 <a href="https://alinex.de">Alexander Schilling</a>
currently about 85% of current browser share is supported by default:
Chrome for Android >= 81
Firefox for Android >= 68
Android >= 81
Chrome >= 73
Edge >= 79
Firefox >= 68
iOS >= 10.0-10.2
Opera >= 64
Safari >= 10.1
1.1.1 Chapters
Read all about the alinex-gui in the chapters (top navigation menu):
Usage - user manual on how to work with examples of screens and explanations
Framework - manual on how to extend functionality
Developer - detailed developer information
If you want to have an offline access to the documentation, feel free to download: alinex-gui.pdf or alinex-gui.epub.
1.1.2 Support
I don't give any paid support but you may create GitLab Issues:
Bug Reports or Feature: Please make sure to give as much information as possible. And explain how the system should behave in
your opinion.
Code Fixing or Extending: If you help to develop this package I am thankful. Develop in a fork and make a merge request after
done. I will have a look at it.
Should anybody be willing to join the core team feel free to ask, too.
Any other comment or discussion as far as it is on this package is also kindly accepted.
Please use for all of them the GitLab Issues which only requires you to register with a free account.
•
•
•
•
•
•
•
•
•
•
•
•
•
•
•
•
1.1.1 Chapters
- 11/67 - Copyright © 2020 - 2021 <a href="https://alinex.de">Alexander Schilling</a>
1.2 Last Changesupdate documentation theme
1.2.1 Version 0.4.2 (08.11.2020)
fix socketio timeout to 5 Minutes
fix changing password / no password
update packages and doc structure
add autologin field to user
replace checks with checkup
1.2.2 Version 0.4.1 (14.10.2020)
fix include flags in build
1.2.3 Version 0.4.0 (12.10.2020)
role administration
sidebar open on access to subentry
fix ability check to also support conditions
roles edit in user view
abilities display for own user
extracted subject possibilities to data module
abilities in info dialog optimized
show users of role and role abilities in user
show disabled state visually
readonly mode if field is not updateable
1.2.4 Version 0.3.0 (31.07.2020)
restructured translation index
updated framework docs
support additional project routes
updated base framework and dropped ie11 support (per default)
faster development mode in parent project
check abilities in router
login placeholder only in demo
start page in sidebar not always highlighted
reset on logout for data cleanup
create/update/delete user now working
1.2.5 Version 0.2.0 (14.06.2020)
automated build
project support
•
•
•
•
•
•
•
•
•
•
•
•
•
•
•
•
•
•
•
•
•
•
•
•
•
•
•
•
•
1.2 Last Changes
- 12/67 - Copyright © 2020 - 2021 <a href="https://alinex.de">Alexander Schilling</a>
authentication
logout
store state in session
check abilities in router
module layout with sidebar
info service
privacy like about dialog
check abilities for menu and icon view
1.2.6 Version 0.1.0 (05.05.2020)
initial setup
i18n
deep structure
language selection
dialog components
modularization
login form
building all aplications
•
•
•
•
•
•
•
•
•
•
•
•
•
•
•
•
1.2.6 Version 0.1.0 (05.05.2020)
- 13/67 - Copyright © 2020 - 2021 <a href="https://alinex.de">Alexander Schilling</a>
1.3 RoadmapWhat's coming next?
That's a big question on a freelancer project. The roadmap may change any time or progress may be stuck. But hopefully I will come
further on in any way.
don't upgrade to @types/node@12 because of electron problem
don't upgrade to socket.io-client v3.0.0 - connection not longer possible
1.3.1 Version 0.5.0
login icon on dashboard
field ability checking
role view
abilities optimizer (on server update)
strict password validation
authentication management (lost-password, email verify)
password generator
1.3.2 Version 1.0.0
google auth or github auth
•
•
•
•
•
•
•
•
•
•
1.3 Roadmap
- 14/67 - Copyright © 2020 - 2021 <a href="https://alinex.de">Alexander Schilling</a>
1.4 Privacy statementThis documentation is part of the alinex.gitlab.io site and as such please have a look on the site's privacy statement.
1.4 Privacy statement
- 15/67 - Copyright © 2020 - 2021 <a href="https://alinex.de">Alexander Schilling</a>
2. Usage
2.1 User ManualThis user manual only describes the Alinex core functionality. Because Alinex is a framework your individual application will have a
lot more, which is not included here.
The core contains:
Layout
Authentication
Access Control Management
System Information
•
•
•
•
2. Usage
- 16/67 - Copyright © 2020 - 2021 <a href="https://alinex.de">Alexander Schilling</a>
2.2 Basic LayoutAt first the system will greet you with a simple dashboard, only containing the few elements available without authentication.
The layout is responsive and also is also possible on mobile phones or tablets.
2.2 Basic Layout
- 17/67 - Copyright © 2020 - 2021 <a href="https://alinex.de">Alexander Schilling</a>
2.2 Basic Layout
- 18/67 - Copyright © 2020 - 2021 <a href="https://alinex.de">Alexander Schilling</a>
To see more a login is necessary. Which will give the user more rights.
2.2 Basic Layout
- 19/67 - Copyright © 2020 - 2021 <a href="https://alinex.de">Alexander Schilling</a>
Now the dashboard on the home page shows some more links.
A dark mode is also possible, which will be set automatically if detectable.
2.2 Basic Layout
- 20/67 - Copyright © 2020 - 2021 <a href="https://alinex.de">Alexander Schilling</a>
2.2 Basic Layout
- 21/67 - Copyright © 2020 - 2021 <a href="https://alinex.de">Alexander Schilling</a>
2.3 AuthenticationIn the moment only a simple email/password authentication is possible. No cookies are needed here.
On logout the client will be reseted, after some seconds.
2.3 Authentication
- 22/67 - Copyright © 2020 - 2021 <a href="https://alinex.de">Alexander Schilling</a>
2.4 Management of Access Control SettingsAs admin you can setup the user rights.
This allows to create and update user records and extend their rights by setting their roles.
To extend the roles with new ones:
2.4 Management of Access Control Settings
- 23/67 - Copyright © 2020 - 2021 <a href="https://alinex.de">Alexander Schilling</a>
You should know the REST interface and data structure to define this. Maybe the swagger is enabled on the server which can help
giving structure information.
2.4 Management of Access Control Settings
- 24/67 - Copyright © 2020 - 2021 <a href="https://alinex.de">Alexander Schilling</a>
2.5 System InformationThe system information is needed to help giving the developer more details about the current setup.
The following information is included:
client with container software and application
user with roles and abilities
server with system user and nodejs version
host hardware and operating system
store databases
•
•
•
•
•
2.5 System Information
- 25/67 - Copyright © 2020 - 2021 <a href="https://alinex.de">Alexander Schilling</a>
3. Technology
3.1 Used TechnologiesWe also don't reinvent the wheel, so we base this framework on some other libraries which we saw as most fitting at the moment we
started this project. It was no selection out of space but with some tryouts before.
The biggest technologies are:
VueJS - A progressive framework for building user interfaces for the web.
Quasar Framework - Composition of VueJS components with a framework to build different apps.
State Management - Vuex Store is used as state management holding the data.
Feathers - Integration of the Feathers Client into VueJS with state management.
All this is based on JavaScript as client language and for the build tool.
Other, smaller modules are also included which are not of such big concern.
•
•
•
•
3. Technology
- 26/67 - Copyright © 2020 - 2021 <a href="https://alinex.de">Alexander Schilling</a>
3.2 Vue.jsVue.js is a web framework for building the view component of web applications.
3.2.1 Basics
Vue.js uses vue files as a base which are a combination of HTML + JavaScript + Style like:
Templates can use:
Binding: Use <a v-bind:href="url"> or <a :href="url"> to bind its value to the given JavaScript expression or variable.
Dynamic Attributes: <a v-bind:[attrName]='url'> or <a :[attrName]='url'> will set the attribute defined within attrName but
no expressions, as attribute names (use computed properties therefore).
Conditionals: <span v-if="flag">Flag is true</span> will show only if flag variable is true. Also the use of v-else or v-else-if
in the direct following element is possible.
Keys: The key attribute within elements tells vue to trade it as individuals.
Show: Using v-show will switch visibiliy, while it is always rendered.
Loops: <li v-for="todo in todos">{{ todo.text }}</li> will add the li tag multiple times.
User Input: <button v-on:click="fn"> or <button @click="fn"> will call function if clicked.
Modifiers: <form v-on:submit.prevent="onSubmit"> will call event.preventDeault()
2-Way-Binding: `
JavaScript
To put some of the complexity out of the template itself and make this simpler, you can add it to the script.
data is a map with values to be directly used.
computed contains an alternative map with functions which will be called at the time of use.
methods are a collection of functions to be called.
computed defines set ter and get ter methods per property which will be called if the property is read or changed.
watch defines functions per properties, which are called if the property has changed.
Computed properties will only update if their used reactive element changes.
Components
The components let a big application be build from smaller and reusable pieces which will be put together hierarchically. To make a
complex application more manageable it should be split into multiple, small units.
https://medium.com/quasar-framework/component-building-with-quasar-fc101b6730ae
<template> <div id="app"> {{ message }} </div></template>
<script>export default { name: 'App', data: { message: 'Hello Vue!' }}</script>
<style>/* possible css styles */</style>
•
•
•
•
•
•
•
•
•
•
•
•
•
•
3.2 Vue.js
- 27/67 - Copyright © 2020 - 2021 <a href="https://alinex.de">Alexander Schilling</a>
3.3 Quasar FrameworkQuasar is a framework based on Vue which makes you write one application which can be build into a desktop website, a desktop app
or a mobile app, or all of them. It comes with a lot of ready to use components and a very powerful build making a small footprint
using tree shaking, code splitting, transpiling and lazy loading.
3.3.1 Structure
3.3.2 Pages and Components
Have a look at the Vue.js description.
As of now TypeScript is supported but makes your live a lot trickier with quasar. It is not as well supported to work fast with it.
Speaking of now, the most examples also are based on ES6 - making it easier for you to just use ES6.
Note
├── public/ # pure static assets (directly copied)├── src/│ ├── assets/ # dynamic assets (processed by webpack)│ ├── components/ # .vue components used in pages & layouts│ ├── css/ # CSS/Stylus/Sass/... files for your app| | ├── app.styl| │ └── quasar.variables.styl # Quasar Stylus variables for you to tweak│ ├── layouts/ # layout .vue files│ ├── pages/ # page .vue files│ ├── boot/ # boot files (app initialization code)│ ├── router/ # Vue Router| | ├── index.js # Vue Router definition| │ └── routes.js # App Routes definitions│ ├── store/ # Vuex Store| | ├── index.js # Vuex Store definition| │ ├── <folder> # Vuex Store Module...| │ └── <folder> # Vuex Store Module...│ ├── App.vue # root Vue component of your App│ └── index.template.html # Template for index.html├── src-ssr/ # SSR specific code (like production Node webserver)├── src-pwa/ # PWA specific code (like Service Worker)├── src-cordova/ # Cordova generated folder used to create Mobile Apps├── src-electron/ # Electron specific code (like "main" thread)├── dist/ # where production builds go│ ├── spa/ # example when building SPA│ ├── ssr/ # example when building SSR│ ├── electron/ # example when building Electron│ └── ....├── quasar.conf.js # Quasar App Config file├── babel.config.js # Babeljs config├── .editorconfig # editor config├── .eslintignore # ESlint ignore paths├── .eslintrc.js # ESlint config├── .postcssrc.js # PostCSS config├── .stylintrc # Stylus lint config├── .gitignore # GIT ignore paths├── package.json # npm scripts and dependencies└── README.md # readme for your website/App
3.3 Quasar Framework
- 28/67 - Copyright © 2020 - 2021 <a href="https://alinex.de">Alexander Schilling</a>
3.4 State ManagementState management allows objects to have one or more states. As a state changes, the correct chain of events will be fired of and
appropriate properties will be modified. By defining and separating the concepts involved in state management and enforcing certain
rules, this gives the code more structure and maintainability.
The basic concept is described below, but you may also visit the Vuex Manual.
3.4.1 Store
The store is basically a container that holds the application state. It is reactive so a change of state will also trigger the change of
elements using this state. And the only way to change state is to commit mutations. This ensures every state change leaves a track-
able record, and enables tooling that helps us better understand the applications.
By providing the store option to the root instance, the store will be injected into all child components of the root and will be available
on them as this.$store :
computed: { count () { return this.$store.state.count }}
3.4 State Management
- 29/67 - Copyright © 2020 - 2021 <a href="https://alinex.de">Alexander Schilling</a>
3.4.2 State
Vuex uses a single state tree including all application level state objects and serves as the "single source of truth". For debugging the
application a snapshot of the state tree may help. To modularize the application the single state tree is split into sub modules.
The mapState helper which generates computed getter functions for us, saving us some keystrokes:
We can also pass a string array to mapState when the name of a mapped computed property is the same as a state sub tree name.
Use the spread operator to mix the outer object with the state object:
3.4.3 Getters
Getters are methods defined in the store to retrieve derived states:
You can then call them using:
The mapGetters helper simply maps store getters to local computed properties:
// in full builds helpers are exposed as Vuex.mapStateimport { mapState } from 'vuex'
export default { // ... computed: mapState({ // arrow functions can make the code very succinct! count: state => state.count, // passing the string value 'count' is same as `state => state.count` countAlias: 'count', })}
computed: mapState([ // map this.count to store.state.count 'count'])
computed: { localComputed () { .... }, ...mapState({ // ... })}
const store = new Vuex.Store({ state: { todos: [ { id: 1, text: '...', done: true }, { id: 2, text: '...', done: false } ] }, getters: { doneTodos: state => { return state.todos.filter(todo => todo.done) } }})
computed: { doneTodosCount () { return this.$store.getters.doneTodosCount }}
import { mapGetters } from 'vuex'
export default { // ... computed: { // mix the getters into computed with object spread operator ...mapGetters([ 'doneTodosCount' ]) }}
3.4.2 State
- 30/67 - Copyright © 2020 - 2021 <a href="https://alinex.de">Alexander Schilling</a>
3.4.4 Mutations
The only way to actually change state in a Vuex store is by committing a mutation. Vuex mutations are very similar to events: each
mutation has a string type and a handler. The handler function is where we perform actual state modifications, and it will receive the
state as the first argument:
The mutation is calles using a commit:
Keep in mind that all mutations have to be synchronous.
Also a helper is there to map mutations directly to component methods:
3.4.5 Actions
Actions are similar to mutations, in reality actions will trigger mutations. The real differences is that they may be asynchronous.
As before you may use mapActions here.
3.4.6 Modules
Due to using a single state tree, all state of our application is contained inside one big object. However, as our application grows in
scale, the store can get really bloated.
To help with that, Vuex allows us to divide our store into modules. Each module can contain its own state, mutations, actions, getters,
and even nested modules. They can be accessed using their complete path like some/nested/module/action .
But VueX also contains a handy method to make namespaced helpers:
const store = new Vuex.Store({ state: { count: 1 }, mutations: { increment (state) { state.count++ } }})
store.commit('increment')
import { mapMutations } from 'vuex'
export default { // ... methods: { ...mapMutations([ 'increment', // map `this.increment()` to `this.$store.commit('increment')`
// `mapMutations` also supports payloads: 'incrementBy' // map `this.incrementBy(amount)` to `this.$store.commit('incrementBy', amount)` ]), ...mapMutations({ add: 'increment' // map `this.add()` to `this.$store.commit('increment')` }) }}
const store = new Vuex.Store({ state: { count: 0 }, mutations: { increment (state) { state.count++ } }, actions: { increment (context) { context.commit('increment') } }})
3.4.4 Mutations
- 31/67 - Copyright © 2020 - 2021 <a href="https://alinex.de">Alexander Schilling</a>
import { createNamespacedHelpers } from 'vuex'
const { mapState, mapActions } = createNamespacedHelpers('some/nested/module')
export default { computed: { // look up in `some/nested/module` ...mapState({ a: a, b: b }) }, methods: { // look up in `some/nested/module` ...mapActions([ 'foo', 'bar' ]) }}
3.4.6 Modules
- 32/67 - Copyright © 2020 - 2021 <a href="https://alinex.de">Alexander Schilling</a>
3.5 Feathers IntegrationThis implements VueX using a feathers based server. It is ready configured to work with the API Server using websockets.
3.5.1 Authentication
This is also ready configured.
3.5.2 Setup Services
Therefore the ServicePlugin is used.
This will be loaded automatically through webpack. Read more about defining the service plugin.
You may access it through this.$FeathersVuex.api.User({})
3.5.3 Store
Getter
list Array - an array of items. The array form of keyedById Read only.
store/services/users.jsimport feathersClient, { makeServicePlugin, BaseModel } from '../../feathers'
class User extends BaseModel { // Required for $FeathersVuex plugin to work after production transpile. static modelName = 'User'; // Define default properties here static instanceDefaults () { return { email: '', password: '' } }}
const servicePath = 'users'const servicePlugin = makeServicePlugin({ Model: User, service: feathersClient.service(servicePath), servicePath})
// Setup the client-side Feathers hooks.feathersClient.service(servicePath).hooks({ before: { all: [], find: [], get: [], create: [], update: [], patch: [], remove: [] }, after: { all: [], find: [], get: [], create: [], update: [], patch: [], remove: [] }, error: { all: [], find: [], get: [], create: [], update: [], patch: [], remove: [] }})
export default servicePlugin
•
3.5 Feathers Integration
- 33/67 - Copyright © 2020 - 2021 <a href="https://alinex.de">Alexander Schilling</a>
find(params) Function - a helper function that allows you to use the Feathers Adapter Common API and Query API to pull data
from the store. This allows you to treat the store just like a local Feathers database adapter (but without hooks).
params Object - an object with a query object and an optional paginate boolean property. The query is in the FeathersJS
query format. You can set params.paginate to false to disable pagination for a single request.
get(id[, params]) Function - a function that allows you to query the store for a single item, by id. It works the same way as get
requests in Feathers database adapters.
id Number|String - the id of the data to be retrieved by id from the store.
params Object - an object containing a Feathers query object.
Mutations
addItem(state, item) - adds a single item to the keyedById map.
item Object - The item to be added to the store.
addItems(state, items) - adds an array of items to the keyedById map.
items Array - the items to be added to the store.
updateItem(state, item) - updates an item in the store to match the passed in item.
item Object - the item, including id, to replace the currently-stored item.
updateItems(state, items) - updates multiple items in the store to match the passed in array of items.
items Array - An array of items.
removeItem(state, item) - removes a single item. item can be
item Number|String|Object - the item or id of the item to be deleted.
removeTemps(state, tempIds) - removes temp records. Also cleans up tempsByNewId
tempIds Array - an array of ids or of objects with tempIds that will be removed from the data store
removeItems(state, items) - Removes the passed in items or ids from the store.
items Array - An array of ids or of objects with ids that will be removed from the data store.
clearAll(state) - clears all data from ids, keyedById, and currentId
Actions
An action is included for each of the Feathers service interface methods. These actions will affect changes in both the Feathers API
server and the Vuex store.
FIND(PARAMS)
Query an array of records from the server and add to the Vuex store.
params Object - an object containing a query object and an optional paginate boolean. You can set params.paginate to false to
disable pagination for a single request.
•
•
•
•
•
You would typically not call these directly, but instead with store.commit('removeItem', 'itemId') . Using vuex's mapMutations on a
Vue component can simplify that to this.removeItem('itemId') .
Note
•
•
•
•
•
•
•
•
•
•
•
•
•
•
•
Because Vuex only supports providing a single argument to actions, there is a slight change in syntax that works well. If you need
to pass multiple arguments to a service method, pass an array to the action with the order of the array elements matching the
order of the arguments.
Note
•
3.5.3 Store
- 34/67 - Copyright © 2020 - 2021 <a href="https://alinex.de">Alexander Schilling</a>
Pagination can be used through the query object:
GET(ID) OR GET([ID, PARAMS])
Query a single record from the server & add to Vuex store
id Number|String - the id of the record being requested from the API server.
params Object - an object containing a query object.
CREATE(DATA|PARAMARRAY)
Create one or multiple records. Note that the method is overloaded to accept two types of arguments. If you want a consistent
interface for creating single or multiple records, use the array syntax, described below. Creating multiple records requires using the
paramArray syntax.
data Object - if an object is provided, a single record will be created.
ParamArray Array - if an array is provided, it is assumed to have this structure. Array containing the two parameters that
Feathers' service.create method accepts.
data {Object|Array} - the data to create. Providing an object creates a single record. Providing an array of objects creates
multiple records.
params {Object} - optional - an object containing a query object. Can be useful in rare situations.
UPDATE(PARAMARRAY)
Update (overwrite) a record.
paramArray Array - array containing the three parameters update accepts.
id Number|String - the id of the existing record being requested from the API server.
data Object - the data that will overwrite the existing record
params Object - an object containing a query object.
Alternatively in a Vue component
const params = {query: {completed: true}}store.dispatch('todos/find', params)
const params = { query: { $limit: 25, $skip: 0 } }store.dispatch('todos/find', params)
•
•
store.dispatch('todos/get', 1)// Use an array to pass paramslet params = {}store.dispatch('todos/get', [1, params])
•
•
•
•
const newTodo = {description: 'write good tests'}store.dispatch('todos/create', newTodo)
•
•
•
•
let data = {id: 5, description: 'write your tests', completed: true}let params = {}// Overwrite item 1 with the above data (FYI: Most databases won't let you change the id.)store.dispatch('todos/update', [1, data, params])
import { mapActions } from 'vuex'export default { methods: { ...mapActions('todos', [ 'update' ]), addTodo () { let data = {id: 5, description: 'write your tests', completed: true} this.update([1, data, {}]) } }}
3.5.3 Store
- 35/67 - Copyright © 2020 - 2021 <a href="https://alinex.de">Alexander Schilling</a>
PATCH(PARAMARRAY)
Patch (merge in changes) one or more records
paramArray Array - array containing the three parameters patch takes.
id Number|String - the id of the existing record being requested from the API server.
data Object - the data that will be merged into the existing record
params Object - an object containing a query object. If params.data is provided, it will be used as the patch data, providing a
simple way to patch with partial data.
REMOVE(ID)
Remove/delete the record with the given id.
id Number|String - the id of the existing record being requested from the API server.
Events
created events will add new record to the store.
patched events will add (if new) or update (if present) the record in the store.
updated events will add (if new) or update (if present) the record in the store.
removed events will remove the record from the store, if present.
3.5.4 Model
Feathers also supports a model which can be used for easy access and while do the interaction with the store. It was already shown
in the code above.
To get the model it should look like:
Static Methods
find(params) - a proxy to the find action.
findInStore(params) - a proxy to the find getter.
get(id, params) - a proxy to the get action. Notice that the signature is more Feathers-like, and doesn't require using an array
to passing both id and params.
getFromStore(id, params) - a proxy to the get getter.
•
•
•
•
let data = {description: 'write your tests', completed: true}let params = {}store.dispatch('todos/patch', [1, data, params])
•
store.dispatch('todos/remove', 1)
•
•
•
•
Within Vue component
Directly from Vue
created () { const { User } = this.$FeathersVuex.api // do something with it}
import Vue from 'vue'
const { Todo } = Vue.$FeathersVuex.api// do something with it
•
•
•
•
3.5.4 Model
- 36/67 - Copyright © 2020 - 2021 <a href="https://alinex.de">Alexander Schilling</a>
Model Events
Setting eventhandlers will be done using:
on - register event handlers to listen to events.
once - register an event handler that only occurs once.
off - remove an event handler.
All FeathersJS events are supported like:
.on('created')
.on('updated')
.on('patched')
.on('removed')
Here’s an example of how to use it in a component:
Instantiation
Calling new User({ firstName: 'Marshall' }) will create the instance with the firstName filled in, already. It will also include defaults
which are set in the model.
Instance Methods
.save() - a convenience wrapper for the create/patch methods. If the records has no _id , the .create() method will be used,
else .patch() .
.create() - calls the create action (service method) using the instance data.
.patch(params) - calls the patch action (service method) using the instance data. You can set params.data as the patch data.
.update() - calls the update action (service method) using the instance data.
.remove() - calls the remove action (service method) using the instance data.
Working with clone
This allows you to make changes to the clone and not update visible data until you commit or save the data.
There's another use case for using .clone() . Vuex has a strict mode that's really useful in development. It throws errors if any
changes occur in the Vuex store state outside of mutations. Clone really comes in handy here, because you can make changes to the
clone without having to write custom Vuex mutations. When you're finished making changes, call .commit() to update the store. This
gives you strict mode compliance with little effort!
.clone() - creates a deep copy of the record and stores it on Model.copiesById .
.commit() - update the data from the clone in the store.
.reset() - reset the clone record to match the one in the store.
•
•
•
•
•
•
•
export default { created() { this.$FeathersVuex.api.Todo.on(‘created’, this.handleTodoCreated) }, destroyed() { this.$FeathersVuex.api.Todo.off(‘created’, this.handleTodoCreated) }, methods: { handleTodoCreated(todo) { console.log(todo) } }}
•
•
•
•
•
•
•
•
const { User } = this.$FeathersVuex.apiconst user = new User({ id: 1, description: 'Do something!' })const userCopy = user.clone()
3.5.4 Model
- 37/67 - Copyright © 2020 - 2021 <a href="https://alinex.de">Alexander Schilling</a>
3.5.5 Components
There are two new renderless data provider components: <FeathersVuexFind> and <FeathersVuexGet> . They simplify performing
queries against the store and/or the API server. They make the data available inside each component's default slot.
The <FeathersVuexFormWrapper> and <FeathersVuexInputWrapper> are renderless components which assist in connecting your
feathers-vuex data to a form.
FeathersVuexFind
The FeathersVuexFind component retrieves data from the API server, puts it in the Vuex store, then transparently retrieves the live,
reactive data from the store and displays it to the user.
By default, the following props are available in the scope data:
items Array - the resulting array of records for find operations.
isFindPending Boolean - when there's an active request to the API server, this will be true.
pagination Object - pagination data from the Vuex store, keyed by the qid attribute. By default, this will be specific to this
component instance.
FeathersVuexGet
The FeathersVuexGet component allows fetching data from directly inside a template. It makes the slot scope available to the child
components.
item Object - the resulting object for get operations
isGetPending Boolean - the same as the isFindPending, but for get requests.
FeathersVuexFormWrapper
The FeathersVuexFormWrapper component uses the "clone and commit" pattern to connect a single record to a child form within its
default slot.
userCopy.description = 'Do something else!'userCopy.commit() // --> Update the data in the store.
console.log(user.description) // --> 'Do something else!'console.log(userCopy.description) // --> 'Do something else!'
<template> <FeathersVuexFind service="users" :query="{}" watch="query"> <section slot-scope="{ items: users }"> {{users}} </section> </FeathersVuexFind></template>
•
•
•
<template> <FeathersVuexGet service="users" :id="id" :watch="id"> <template slot-scope="{ item: user }"> {{ user }} </template> </FeathersVuexGet></template>
•
•
<template> <FeathersVuexFormWrapper :item="currentItem" watch> <template v-slot="{ clone, save, reset, remove }"> <SomeEditor :item="clone" @save="save().then(handleSaveResponse)" @reset="reset" @remove="remove" ></SomeEditor> </template> </FeathersVuexFormWrapper></template>
<script>
3.5.5 Components
- 38/67 - Copyright © 2020 - 2021 <a href="https://alinex.de">Alexander Schilling</a>
Props:
item Object - a model instance from the Vuex store.
watch Boolean|Array - when enabled, if the original record is updated, the data will be re-cloned.
eager Boolean - while this is enabled, using the save method will first commit the result to the store then it will send a network
request. The UI display will update immediately, without waiting for any response from the API server. Default: true.
Slot:
clone Object - the cloned record. Each record in the store can have a single clone. The clones are stored on the service's model
class, by default.
save Function - when called, it commits the data and saves the record (with eager updating, by default. See the eager prop.)
reset Function - when called, the clone data will be reset back to the data that is currently found in the store for the same
record.
remove Function - when called, it removes the record from the API server and the Vuex store.
FeathersVuexInputWrapper
Building on the same ideas as the FeathersVuexFormWrapper, the FeathersVuexInputWrapper reduces boilerplate for working with
the clone and commit pattern on a single input. One use case for this component is implementing an "edit-in-place" workflow. The
following example shows how to use the FeathersVuexInputWrapper to automatically save a record upon blur on a text input:
import { FeathersVuexFormWrapper } from 'feathers-vuex'
export default { name: 'MyComponent', components: { FeathersVuexFormWrapper }, props: { currentItem: { type: Object, required: true } }, methods: { handleSaveReponse(savedItem) { console.log(savedItem) // The item returned from the API call } }}</script>
•
•
•
•
•
•
•
<template> <div class="p-3"> <FeathersVuexInputWrapper :item="user" prop="email"> <template #default="{ current, prop, createClone, handler }"> <input v-model="current[prop]" type="text" @focus="createClone" @blur="e => handler(e, save)" /> </template> </FeathersVuexInputWrapper>
<!-- Simple readout to show that it's working. --> <pre class="bg-black text-white text-xs mt-2 p-1">{{user}}</pre> </div></template>
<script>export default { name: 'InputWrapperExample', methods: { // Optionally make the event handler async. async save({ event, clone, prop, data }) { const user = clone.commit() return user.patch(data) } }}</script>
3.5.5 Components
- 39/67 - Copyright © 2020 - 2021 <a href="https://alinex.de">Alexander Schilling</a>
Props:
item - the original (non-cloned) model instance.
prop - the property name on the model instance to be edited.
Slot Scope:
Only the default slot is used. The following props are available in the slot scope:
current clone|instance - returns the clone if it exists, or the original record. current = clone || item
clone clone - the internal clone. This is exposed for debugging purposes.
prop String - the value of the prop prop. If you have the prop stored in a variable in the outer scope, this is redundant and not
needed. You could just use this from the outer scope. It mostly comes in handy when you are manually specifying the prop name
on the component.
createClone Function - sets up the internal clone. Meant to be used as an event handler.
handler Function - has the signature handler(event, callback). It prepared data before calling the callback function that must be
provided from the outer scope.
•
•
•
•
•
•
•
3.5.5 Components
- 40/67 - Copyright © 2020 - 2021 <a href="https://alinex.de">Alexander Schilling</a>
4. Framework
4.1 Core FrameworkAs the Alinex GUI is no ready to use frontend, you have to use it as framework and build your real application with it. This section will
describe the core GUI framework with it's components. To extend it best is to make a parent project like described under
development.
4.1.1 Features
Internationalization
Dynamic loading using import in router
4.1.2 3rd Party Packages
Additional to the short descriptions here the original documentation may be useful:
Vue
VueX for application state
VueX Feathers
Quasar Framework
Style
Icons: Material or MDI using mdi-xxx
This is not a complete manual, you have to know the basics of the fundamental technologies on which the GUI is build.
Attention
•
•
•
•
•
•
•
•
4. Framework
- 41/67 - Copyright © 2020 - 2021 <a href="https://alinex.de">Alexander Schilling</a>
4.2 Setup
4.2.1 Configuration
The configuration in package.json will define which browsers are supported using more or less polyfills. In example you may add old
MS Internet Explorer 11 Support with:
4.2.2 Builds
A development build is possible using:
This enables you to change things and see the result in the browser in ashort time. But because the projects data has to be rejoined
with the core each time it is not as fast as in single projects. It automatically runs a local server which will reload and also refresh the
browser if changes are made.
If everything looks fine you may build the different apps using npm run build , which will call the bin/gui-setup .
To specify the server to use which will be build into the client in the download files use the environment setting API :
It will create distributable files within the dist folder which may be published to be used. But often you will call this from the Alinex
Server or the project specific module extending it.
Browser
To run the web versions a web server is needed, which can be simulated using:
or
But first you have to build it like described above.
SINGLE PAGE APPLICATION
A Single-Page Application (SPA) is a web application or web site that interacts with the user by dynamically rewriting the current
page rather than loading entire new pages from a server. This approach avoids interruption of the user experience between
successive pages, making the application behave more like a desktop application. But the initial loading will take some time.
In a SPA the appropriate resources are dynamically loaded and added to the page as necessary, usually in response to user actions.
The page does not reload at any point in the process, nor does control transfer to another page, although the location hash or the
HTML5 History API can be used to provide the perception and navigability of separate logical pages in the application.
This is build under dist folder.
PROGRESSIVE WEB APPLICATION
A Progressive Web App (PWA) is a web app that uses modern web capabilities to deliver an app-like experience to users.
As PWA the application will be mostly connectivity independent by using service workers to work offline or on low quality networks.
"browserslist": [ "ie 11", // <<-- add it ...]
npm run dev
bin/gui-setup
bin/gui-setup --api http://my-sever.de:3000
node_modules/.bin/quasar serve dist
PORT=8000 node_modules/.bin/quasar serve dist
4.2 Setup
- 42/67 - Copyright © 2020 - 2021 <a href="https://alinex.de">Alexander Schilling</a>
This alternative browser version is not used and build at the moment.
Mobile Apps
Using Capacitor the web applications is transformed into a mobile app. It exposes native device APIs in the form of JavaScript
modules.
If this is needed some preparations has to be done.
Here you can build for two devices.
ANDROID APP
You need to install the Android SDK with cli tools and licenses agreed before gui-setup can successfully build it.
You will find the APK under dist/download folder.
IOS APP
This is only possible to be made on a MacOS host with Xcode installed.
Desktop Applications
To make also an desktop app the JavaScript is bundled into an electron app. Electron is the main GUI framework behind several
notable open-source projects including GitHub’s Atom and Microsoft’s Visual Studio Code source code editors.
You will find the linux, darwin and win32 applications under dist/download folder.
Dokumentation
The documentation (html+pdf+epub) will be build using:
But you can also always run a development server of the documentation (mkdocs needs to be installed):
This will start an auto reloading documentation on http://127.0.0.1:8000
npm run pdf
mkdocs serve
4.2.2 Builds
- 43/67 - Copyright © 2020 - 2021 <a href="https://alinex.de">Alexander Schilling</a>
4.3 LayoutThe whole application has a general 'default' layout which is used throughout most pages. It defines the basic page layout with:
drop-down menu (account and settings)
title bar
side bar
content area
Because the whole application is a single page, the title bar of the browser normally never changes. This is solved by setting it from
meta information which will be gathered from route.meta.module through i18n transtaltion using *.title and appending the global
title. This can be overwritten by setting meta.title in the page.
As styling language we use Sass in the modern form of SCSS, but other style processors are also possible.
4.3.1 Icons
Within the UI a lot of icons are used for better visualization. We won't mix to much different styles here so as far as possible we use
one of:
Material Icons (no prefix needed)
Material Design Icons (prefix mdi- is needed)
4.3.2 Dark Mode
The application supports a light mode (default) as well as a default, which may be switched within the settings (top right).
To support this two modes use the following css classes:
.body--light or .body--dark is added automaticaly to the body tag. Use them in the stylesheets to setup a specific style using
light or dark colors.
Also if a element has the class .light or .dark attached it will only be displayed in the specified mode.
4.3.3 Sass Variables
•
•
•
•
•
•
•
•
$space-base : 16px !default$space-x-base : $space-base !default$space-y-base : $space-base !default
$space-none : (x: 0, y: 0) !default$space-xs : (x: ($space-x-base * .25), y: ($space-y-base * .25)) !default$space-sm : (x: ($space-x-base * .5), y: ($space-y-base * .5)) !default$space-md : (x: $space-x-base, y: $space-y-base) !default$space-lg : (x: ($space-x-base * 1.5), y: ($space-y-base * 1.5)) !default$space-xl : (x: ($space-x-base * 3), y: ($space-y-base * 3)) !default
// sorry for long line; we need .sass and it doesn't support multi-line list$spaces: ('none': $space-none, 'xs': $space-xs, 'sm': $space-sm, 'md': $space-md, 'lg': $space-lg, 'xl': $space-xl) !default
// Max width at which point// current size ends$breakpoint-xs: 599px !default$breakpoint-sm: 1023px !default$breakpoint-md: 1439px !default$breakpoint-lg: 1919px !default
$flex-cols : 12 !default$flex-gutter-xs : ($space-base * .25) !default$flex-gutter-sm : ($space-base * .5) !default$flex-gutter-md : $space-base !default$flex-gutter-lg : ($space-base * 1.5) !default$flex-gutter-xl : ($space-base * 3) !default
$body-font-size : 14px !default$body-line-height : 1.5 !default
// sorry for long line; we need .sass and it doesn't support multi-line list$flex-gutter: ('none': 0, 'xs': $flex-gutter-xs, 'sm': $flex-gutter-sm, 'md': $flex-gutter-md, 'lg': $flex-gutter-lg, 'xl': $flex-gutter-xl) !default
4.3 Layout
- 44/67 - Copyright © 2020 - 2021 <a href="https://alinex.de">Alexander Schilling</a>
// sorry for long line; we need .sass and it doesn't support multi-line list$sizes: ('xs': 0, 'sm': ($breakpoint-xs + 1), 'md': ($breakpoint-sm + 1), 'lg': ($breakpoint-md + 1), 'xl': ($breakpoint-lg + 1)) !default
$breakpoint-xs-min: 0 !default$breakpoint-xs-max: $breakpoint-xs !default
$breakpoint-sm-min: map-get($sizes, "sm") !default$breakpoint-sm-max: $breakpoint-sm !default
$breakpoint-md-min: map-get($sizes, "md") !default$breakpoint-md-max: $breakpoint-md !default
$breakpoint-lg-min: map-get($sizes, "lg") !default$breakpoint-lg-max: $breakpoint-lg !default
$breakpoint-xl-min: map-get($sizes, "xl") !default$breakpoint-xl-max: 9999px !default
$h1: (size: 6rem, line-height: 6rem, letter-spacing: -.01562em, weight: 300) !default$h2: (size: 3.75rem, line-height: 3.75rem, letter-spacing: -.00833em, weight: 300) !default$h3: (size: 3rem, line-height: 3.125rem, letter-spacing: normal, weight: 400) !default$h4: (size: 2.125rem, line-height: 2.5rem, letter-spacing: .00735em, weight: 400) !default$h5: (size: 1.5rem, line-height: 2rem, letter-spacing: normal, weight: 400) !default$h6: (size: 1.25rem, line-height: 2rem, letter-spacing: .0125em, weight: 500) !default$subtitle1: (size: 1rem, line-height: 1.75rem, letter-spacing: .00937em, weight: 400) !default$subtitle2: (size: .875rem, line-height: 1.375rem, letter-spacing: .00714em, weight: 500) !default$body1: (size: 1rem, line-height: 1.5rem, letter-spacing: .03125em, weight: 400) !default$body2: (size: .875rem, line-height: 1.25rem, letter-spacing: .01786em, weight: 400) !default$overline: (size: .75rem, line-height: 2rem, letter-spacing: .16667em, weight: 500) !default$caption: (size: .75rem, line-height: 1.25rem, letter-spacing: .03333em, weight: 400) !default
// sorry for long line; we need .sass and it doesn't support multi-line list$headings: ('h1': $h1, 'h2': $h2, 'h3': $h3, 'h4': $h4, 'h5': $h5, 'h6': $h6, 'subtitle1': $subtitle1, 'subtitle2': $subtitle2, 'body1': $body1, 'body2': $body2, 'overline': $overline, 'caption': $caption) !default
// sorry for long line; we need .sass and it doesn't support multi-line list$h-tags: (h1: map-get($headings, "h1"), h2: map-get($headings, "h2"), h3: map-get($headings, "h3"), h4: map-get($headings, "h4"), h5: map-get($headings, "h5"), h6: map-get($headings, "h6")) !default
// sorry for long line; we need .sass and it doesn't support multi-line list$text-weights: (thin: 100, light: 300, regular: 400, medium: 500, bold: 700, bolder: 900) !default
$primary : #1976D2 !default$secondary : #26A69A !default$accent : #9C27B0 !default
// used by dark mode$dark-page : #121212 !default$dark : #1d1d1d !default
$positive : #21BA45 !default$negative : #C10015 !default$info : #31CCEC !default$warning : #F2C037 !default
$dimmed-background : rgba(0, 0, 0, .4) !default$light-dimmed-background : rgba(255, 255, 255, .6) !default
$separator-color : rgba(0, 0, 0, .12) !default$separator-dark-color : rgba(255, 255, 255, .28) !default
$red : #f44336 !default$red-1 : #ffebee !default$red-2 : #ffcdd2 !default$red-3 : #ef9a9a !default$red-4 : #e57373 !default$red-5 : #ef5350 !default$red-6 : #f44336 !default$red-7 : #e53935 !default$red-8 : #d32f2f !default$red-9 : #c62828 !default$red-10 : #b71c1c !default$red-11 : #ff8a80 !default$red-12 : #ff5252 !default$red-13 : #ff1744 !default$red-14 : #d50000 !default$pink : #e91e63 !default$pink-1 : #fce4ec !default$pink-2 : #f8bbd0 !default$pink-3 : #f48fb1 !default$pink-4 : #f06292 !default$pink-5 : #ec407a !default$pink-6 : #e91e63 !default$pink-7 : #d81b60 !default$pink-8 : #c2185b !default$pink-9 : #ad1457 !default$pink-10 : #880e4f !default$pink-11 : #ff80ab !default$pink-12 : #ff4081 !default$pink-13 : #f50057 !default$pink-14 : #c51162 !default$purple : #9c27b0 !default$purple-1 : #f3e5f5 !default
4.3.3 Sass Variables
- 45/67 - Copyright © 2020 - 2021 <a href="https://alinex.de">Alexander Schilling</a>
$purple-2 : #e1bee7 !default$purple-3 : #ce93d8 !default$purple-4 : #ba68c8 !default$purple-5 : #ab47bc !default$purple-6 : #9c27b0 !default$purple-7 : #8e24aa !default$purple-8 : #7b1fa2 !default$purple-9 : #6a1b9a !default$purple-10 : #4a148c !default$purple-11 : #ea80fc !default$purple-12 : #e040fb !default$purple-13 : #d500f9 !default$purple-14 : #aa00ff !default$deep-purple : #673ab7 !default$deep-purple-1 : #ede7f6 !default$deep-purple-2 : #d1c4e9 !default$deep-purple-3 : #b39ddb !default$deep-purple-4 : #9575cd !default$deep-purple-5 : #7e57c2 !default$deep-purple-6 : #673ab7 !default$deep-purple-7 : #5e35b1 !default$deep-purple-8 : #512da8 !default$deep-purple-9 : #4527a0 !default$deep-purple-10 : #311b92 !default$deep-purple-11 : #b388ff !default$deep-purple-12 : #7c4dff !default$deep-purple-13 : #651fff !default$deep-purple-14 : #6200ea !default$indigo : #3f51b5 !default$indigo-1 : #e8eaf6 !default$indigo-2 : #c5cae9 !default$indigo-3 : #9fa8da !default$indigo-4 : #7986cb !default$indigo-5 : #5c6bc0 !default$indigo-6 : #3f51b5 !default$indigo-7 : #3949ab !default$indigo-8 : #303f9f !default$indigo-9 : #283593 !default$indigo-10 : #1a237e !default$indigo-11 : #8c9eff !default$indigo-12 : #536dfe !default$indigo-13 : #3d5afe !default$indigo-14 : #304ffe !default$blue : #2196f3 !default$blue-1 : #e3f2fd !default$blue-2 : #bbdefb !default$blue-3 : #90caf9 !default$blue-4 : #64b5f6 !default$blue-5 : #42a5f5 !default$blue-6 : #2196f3 !default$blue-7 : #1e88e5 !default$blue-8 : #1976d2 !default$blue-9 : #1565c0 !default$blue-10 : #0d47a1 !default$blue-11 : #82b1ff !default$blue-12 : #448aff !default$blue-13 : #2979ff !default$blue-14 : #2962ff !default$light-blue : #03a9f4 !default$light-blue-1 : #e1f5fe !default$light-blue-2 : #b3e5fc !default$light-blue-3 : #81d4fa !default$light-blue-4 : #4fc3f7 !default$light-blue-5 : #29b6f6 !default$light-blue-6 : #03a9f4 !default$light-blue-7 : #039be5 !default$light-blue-8 : #0288d1 !default$light-blue-9 : #0277bd !default$light-blue-10 : #01579b !default$light-blue-11 : #80d8ff !default$light-blue-12 : #40c4ff !default$light-blue-13 : #00b0ff !default$light-blue-14 : #0091ea !default$cyan : #00bcd4 !default$cyan-1 : #e0f7fa !default$cyan-2 : #b2ebf2 !default$cyan-3 : #80deea !default$cyan-4 : #4dd0e1 !default$cyan-5 : #26c6da !default$cyan-6 : #00bcd4 !default$cyan-7 : #00acc1 !default$cyan-8 : #0097a7 !default$cyan-9 : #00838f !default$cyan-10 : #006064 !default$cyan-11 : #84ffff !default$cyan-12 : #18ffff !default$cyan-13 : #00e5ff !default$cyan-14 : #00b8d4 !default$teal : #009688 !default$teal-1 : #e0f2f1 !default$teal-2 : #b2dfdb !default$teal-3 : #80cbc4 !default$teal-4 : #4db6ac !default$teal-5 : #26a69a !default
4.3.3 Sass Variables
- 46/67 - Copyright © 2020 - 2021 <a href="https://alinex.de">Alexander Schilling</a>
$teal-6 : #009688 !default$teal-7 : #00897b !default$teal-8 : #00796b !default$teal-9 : #00695c !default$teal-10 : #004d40 !default$teal-11 : #a7ffeb !default$teal-12 : #64ffda !default$teal-13 : #1de9b6 !default$teal-14 : #00bfa5 !default$green : #4caf50 !default$green-1 : #e8f5e9 !default$green-2 : #c8e6c9 !default$green-3 : #a5d6a7 !default$green-4 : #81c784 !default$green-5 : #66bb6a !default$green-6 : #4caf50 !default$green-7 : #43a047 !default$green-8 : #388e3c !default$green-9 : #2e7d32 !default$green-10 : #1b5e20 !default$green-11 : #b9f6ca !default$green-12 : #69f0ae !default$green-13 : #00e676 !default$green-14 : #00c853 !default$light-green : #8bc34a !default$light-green-1 : #f1f8e9 !default$light-green-2 : #dcedc8 !default$light-green-3 : #c5e1a5 !default$light-green-4 : #aed581 !default$light-green-5 : #9ccc65 !default$light-green-6 : #8bc34a !default$light-green-7 : #7cb342 !default$light-green-8 : #689f38 !default$light-green-9 : #558b2f !default$light-green-10 : #33691e !default$light-green-11 : #ccff90 !default$light-green-12 : #b2ff59 !default$light-green-13 : #76ff03 !default$light-green-14 : #64dd17 !default$lime : #cddc39 !default$lime-1 : #f9fbe7 !default$lime-2 : #f0f4c3 !default$lime-3 : #e6ee9c !default$lime-4 : #dce775 !default$lime-5 : #d4e157 !default$lime-6 : #cddc39 !default$lime-7 : #c0ca33 !default$lime-8 : #afb42b !default$lime-9 : #9e9d24 !default$lime-10 : #827717 !default$lime-11 : #f4ff81 !default$lime-12 : #eeff41 !default$lime-13 : #c6ff00 !default$lime-14 : #aeea00 !default$yellow : #ffeb3b !default$yellow-1 : #fffde7 !default$yellow-2 : #fff9c4 !default$yellow-3 : #fff59d !default$yellow-4 : #fff176 !default$yellow-5 : #ffee58 !default$yellow-6 : #ffeb3b !default$yellow-7 : #fdd835 !default$yellow-8 : #fbc02d !default$yellow-9 : #f9a825 !default$yellow-10 : #f57f17 !default$yellow-11 : #ffff8d !default$yellow-12 : #ffff00 !default$yellow-13 : #ffea00 !default$yellow-14 : #ffd600 !default$amber : #ffc107 !default$amber-1 : #fff8e1 !default$amber-2 : #ffecb3 !default$amber-3 : #ffe082 !default$amber-4 : #ffd54f !default$amber-5 : #ffca28 !default$amber-6 : #ffc107 !default$amber-7 : #ffb300 !default$amber-8 : #ffa000 !default$amber-9 : #ff8f00 !default$amber-10 : #ff6f00 !default$amber-11 : #ffe57f !default$amber-12 : #ffd740 !default$amber-13 : #ffc400 !default$amber-14 : #ffab00 !default$orange : #ff9800 !default$orange-1 : #fff3e0 !default$orange-2 : #ffe0b2 !default$orange-3 : #ffcc80 !default$orange-4 : #ffb74d !default$orange-5 : #ffa726 !default$orange-6 : #ff9800 !default$orange-7 : #fb8c00 !default$orange-8 : #f57c00 !default$orange-9 : #ef6c00 !default
4.3.3 Sass Variables
- 47/67 - Copyright © 2020 - 2021 <a href="https://alinex.de">Alexander Schilling</a>
$orange-10 : #e65100 !default$orange-11 : #ffd180 !default$orange-12 : #ffab40 !default$orange-13 : #ff9100 !default$orange-14 : #ff6d00 !default$deep-orange : #ff5722 !default$deep-orange-1 : #fbe9e7 !default$deep-orange-2 : #ffccbc !default$deep-orange-3 : #ffab91 !default$deep-orange-4 : #ff8a65 !default$deep-orange-5 : #ff7043 !default$deep-orange-6 : #ff5722 !default$deep-orange-7 : #f4511e !default$deep-orange-8 : #e64a19 !default$deep-orange-9 : #d84315 !default$deep-orange-10 : #bf360c !default$deep-orange-11 : #ff9e80 !default$deep-orange-12 : #ff6e40 !default$deep-orange-13 : #ff3d00 !default$deep-orange-14 : #dd2c00 !default$brown : #795548 !default$brown-1 : #efebe9 !default$brown-2 : #d7ccc8 !default$brown-3 : #bcaaa4 !default$brown-4 : #a1887f !default$brown-5 : #8d6e63 !default$brown-6 : #795548 !default$brown-7 : #6d4c41 !default$brown-8 : #5d4037 !default$brown-9 : #4e342e !default$brown-10 : #3e2723 !default$brown-11 : #d7ccc8 !default$brown-12 : #bcaaa4 !default$brown-13 : #8d6e63 !default$brown-14 : #5d4037 !default$grey : #9e9e9e !default$grey-1 : #fafafa !default$grey-2 : #f5f5f5 !default$grey-3 : #eeeeee !default$grey-4 : #e0e0e0 !default$grey-5 : #bdbdbd !default$grey-6 : #9e9e9e !default$grey-7 : #757575 !default$grey-8 : #616161 !default$grey-9 : #424242 !default$grey-10 : #212121 !default$grey-11 : #f5f5f5 !default$grey-12 : #eeeeee !default$grey-13 : #bdbdbd !default$grey-14 : #616161 !default$blue-grey : #607d8b !default$blue-grey-1 : #eceff1 !default$blue-grey-2 : #cfd8dc !default$blue-grey-3 : #b0bec5 !default$blue-grey-4 : #90a4ae !default$blue-grey-5 : #78909c !default$blue-grey-6 : #607d8b !default$blue-grey-7 : #546e7a !default$blue-grey-8 : #455a64 !default$blue-grey-9 : #37474f !default$blue-grey-10 : #263238 !default$blue-grey-11 : #cfd8dc !default$blue-grey-12 : #b0bec5 !default$blue-grey-13 : #78909c !default$blue-grey-14 : #455a64 !default
$ios-statusbar-height : 20px !default
$z-fab : 990 !default$z-side : 1000 !default$z-marginals : 2000 !default$z-fixed-drawer : 3000 !default$z-fullscreen : 6000 !default$z-menu : 6000 !default$z-top : 7000 !default$z-tooltip : 9000 !default$z-notify : 9500 !default$z-max : 9998 !default
$shadow-color : #000 !default$shadow-transition : box-shadow .28s cubic-bezier(.4, 0, .2, 1) !default$inset-shadow : 0 7px 9px -7px rgba($shadow-color, .7) inset !default
$elevation-umbra : rgba($shadow-color, .2) !default$elevation-penumbra : rgba($shadow-color, .14) !default$elevation-ambient : rgba($shadow-color, .12) !default
$shadow-0 : 0 0 0 $elevation-umbra, 0 0 0 $elevation-penumbra, 0 0 0 $elevation-ambient !default$shadow-1 : 0 1px 3px $elevation-umbra, 0 1px 1px $elevation-penumbra, 0 2px 1px -1px $elevation-ambient !default$shadow-2 : 0 1px 5px $elevation-umbra, 0 2px 2px $elevation-penumbra, 0 3px 1px -2px $elevation-ambient !default$shadow-3 : 0 1px 8px $elevation-umbra, 0 3px 4px $elevation-penumbra, 0 3px 3px -2px $elevation-ambient !default$shadow-4 : 0 2px 4px -1px $elevation-umbra, 0 4px 5px $elevation-penumbra, 0 1px 10px $elevation-ambient !default$shadow-5 : 0 3px 5px -1px $elevation-umbra, 0 5px 8px $elevation-penumbra, 0 1px 14px $elevation-ambient !default$shadow-6 : 0 3px 5px -1px $elevation-umbra, 0 6px 10px $elevation-penumbra, 0 1px 18px $elevation-ambient !default
4.3.3 Sass Variables
- 48/67 - Copyright © 2020 - 2021 <a href="https://alinex.de">Alexander Schilling</a>
$shadow-7 : 0 4px 5px -2px $elevation-umbra, 0 7px 10px 1px $elevation-penumbra, 0 2px 16px 1px $elevation-ambient !default$shadow-8 : 0 5px 5px -3px $elevation-umbra, 0 8px 10px 1px $elevation-penumbra, 0 3px 14px 2px $elevation-ambient !default$shadow-9 : 0 5px 6px -3px $elevation-umbra, 0 9px 12px 1px $elevation-penumbra, 0 3px 16px 2px $elevation-ambient !default$shadow-10 : 0 6px 6px -3px $elevation-umbra, 0 10px 14px 1px $elevation-penumbra, 0 4px 18px 3px $elevation-ambient !default$shadow-11 : 0 6px 7px -4px $elevation-umbra, 0 11px 15px 1px $elevation-penumbra, 0 4px 20px 3px $elevation-ambient !default$shadow-12 : 0 7px 8px -4px $elevation-umbra, 0 12px 17px 2px $elevation-penumbra, 0 5px 22px 4px $elevation-ambient !default$shadow-13 : 0 7px 8px -4px $elevation-umbra, 0 13px 19px 2px $elevation-penumbra, 0 5px 24px 4px $elevation-ambient !default$shadow-14 : 0 7px 9px -4px $elevation-umbra, 0 14px 21px 2px $elevation-penumbra, 0 5px 26px 4px $elevation-ambient !default$shadow-15 : 0 8px 9px -5px $elevation-umbra, 0 15px 22px 2px $elevation-penumbra, 0 6px 28px 5px $elevation-ambient !default$shadow-16 : 0 8px 10px -5px $elevation-umbra, 0 16px 24px 2px $elevation-penumbra, 0 6px 30px 5px $elevation-ambient !default$shadow-17 : 0 8px 11px -5px $elevation-umbra, 0 17px 26px 2px $elevation-penumbra, 0 6px 32px 5px $elevation-ambient !default$shadow-18 : 0 9px 11px -5px $elevation-umbra, 0 18px 28px 2px $elevation-penumbra, 0 7px 34px 6px $elevation-ambient !default$shadow-19 : 0 9px 12px -6px $elevation-umbra, 0 19px 29px 2px $elevation-penumbra, 0 7px 36px 6px $elevation-ambient !default$shadow-20 : 0 10px 13px -6px $elevation-umbra, 0 20px 31px 3px $elevation-penumbra, 0 8px 38px 7px $elevation-ambient !default$shadow-21 : 0 10px 13px -6px $elevation-umbra, 0 21px 33px 3px $elevation-penumbra, 0 8px 40px 7px $elevation-ambient !default$shadow-22 : 0 10px 14px -6px $elevation-umbra, 0 22px 35px 3px $elevation-penumbra, 0 8px 42px 7px $elevation-ambient !default$shadow-23 : 0 11px 14px -7px $elevation-umbra, 0 23px 36px 3px $elevation-penumbra, 0 9px 44px 8px $elevation-ambient !default$shadow-24 : 0 11px 15px -7px $elevation-umbra, 0 24px 38px 3px $elevation-penumbra, 0 9px 46px 8px $elevation-ambient !default
// sorry for long line; we need .sass and it doesn't support multi-line list$shadows: ($shadow-1, $shadow-2, $shadow-3, $shadow-4, $shadow-5, $shadow-6, $shadow-7, $shadow-8, $shadow-9, $shadow-10, $shadow-11, $shadow-12, $shadow-13, $shadow-14, $shadow-15, $shadow-16, $shadow-17, $shadow-18, $shadow-19, $shadow-20, $shadow-21, $shadow-22, $shadow-23, $shadow-24)
$shadow-up-0 : 0 0 0 $elevation-umbra, 0 0 0 $elevation-penumbra, 0 0 0 $elevation-ambient !default$shadow-up-1 : 0 -1px 3px $elevation-umbra, 0 -1px 1px $elevation-penumbra, 0 -2px 1px -1px $elevation-ambient !default$shadow-up-2 : 0 -1px 5px $elevation-umbra, 0 -2px 2px $elevation-penumbra, 0 -3px 1px -2px $elevation-ambient !default$shadow-up-3 : 0 -1px 8px $elevation-umbra, 0 -3px 4px $elevation-penumbra, 0 -3px 3px -2px $elevation-ambient !default$shadow-up-4 : 0 -2px 4px -1px $elevation-umbra, 0 -4px 5px $elevation-penumbra, 0 -1px 10px $elevation-ambient !default$shadow-up-5 : 0 -3px 5px -1px $elevation-umbra, 0 -5px 8px $elevation-penumbra, 0 -1px 14px $elevation-ambient !default$shadow-up-6 : 0 -3px 5px -1px $elevation-umbra, 0 -6px 10px $elevation-penumbra, 0 -1px 18px $elevation-ambient !default$shadow-up-7 : 0 -4px 5px -2px $elevation-umbra, 0 -7px 10px 1px $elevation-penumbra, 0 -2px 16px 1px $elevation-ambient !default$shadow-up-8 : 0 -5px 5px -3px $elevation-umbra, 0 -8px 10px 1px $elevation-penumbra, 0 -3px 14px 2px $elevation-ambient !default$shadow-up-9 : 0 -5px 6px -3px $elevation-umbra, 0 -9px 12px 1px $elevation-penumbra, 0 -3px 16px 2px $elevation-ambient !default$shadow-up-10 : 0 -6px 6px -3px $elevation-umbra, 0 -10px 14px 1px $elevation-penumbra, 0 -4px 18px 3px $elevation-ambient !default$shadow-up-11 : 0 -6px 7px -4px $elevation-umbra, 0 -11px 15px 1px $elevation-penumbra, 0 -4px 20px 3px $elevation-ambient !default$shadow-up-12 : 0 -7px 8px -4px $elevation-umbra, 0 -12px 17px 2px $elevation-penumbra, 0 -5px 22px 4px $elevation-ambient !default$shadow-up-13 : 0 -7px 8px -4px $elevation-umbra, 0 -13px 19px 2px $elevation-penumbra, 0 -5px 24px 4px $elevation-ambient !default$shadow-up-14 : 0 -7px 9px -4px $elevation-umbra, 0 -14px 21px 2px $elevation-penumbra, 0 -5px 26px 4px $elevation-ambient !default$shadow-up-15 : 0 -8px 9px -5px $elevation-umbra, 0 -15px 22px 2px $elevation-penumbra, 0 -6px 28px 5px $elevation-ambient !default$shadow-up-16 : 0 -8px 10px -5px $elevation-umbra, 0 -16px 24px 2px $elevation-penumbra, 0 -6px 30px 5px $elevation-ambient !default$shadow-up-17 : 0 -8px 11px -5px $elevation-umbra, 0 -17px 26px 2px $elevation-penumbra, 0 -6px 32px 5px $elevation-ambient !default$shadow-up-18 : 0 -9px 11px -5px $elevation-umbra, 0 -18px 28px 2px $elevation-penumbra, 0 -7px 34px 6px $elevation-ambient !default$shadow-up-19 : 0 -9px 12px -6px $elevation-umbra, 0 -19px 29px 2px $elevation-penumbra, 0 -7px 36px 6px $elevation-ambient !default$shadow-up-20 : 0 -10px 13px -6px $elevation-umbra, 0 -20px 31px 3px $elevation-penumbra, 0 -8px 38px 7px $elevation-ambient !default$shadow-up-21 : 0 -10px 13px -6px $elevation-umbra, 0 -21px 33px 3px $elevation-penumbra, 0 -8px 40px 7px $elevation-ambient !default$shadow-up-22 : 0 -10px 14px -6px $elevation-umbra, 0 -22px 35px 3px $elevation-penumbra, 0 -8px 42px 7px $elevation-ambient !default$shadow-up-23 : 0 -11px 14px -7px $elevation-umbra, 0 -23px 36px 3px $elevation-penumbra, 0 -9px 44px 8px $elevation-ambient !default$shadow-up-24 : 0 -11px 15px -7px $elevation-umbra, 0 -24px 38px 3px $elevation-penumbra, 0 -9px 46px 8px $elevation-ambient !default
// sorry for long line; we need .sass and it doesn't support multi-line list$shadows-up: ($shadow-up-1, $shadow-up-2, $shadow-up-3, $shadow-up-4, $shadow-up-5, $shadow-up-6, $shadow-up-7, $shadow-up-8, $shadow-up-9, $shadow-up-10, $shadow-up-11, $shadow-up-12, $shadow-up-13, $shadow-up-14, $shadow-up-15, $shadow-up-16, $shadow-up-17, $shadow-up-18, $shadow-up-19, $shadow-up-20, $shadow-up-21, $shadow-up-22, $shadow-up-23, $shadow-up-24)
$generic-border-radius : 4px !default$generic-hover-transition : .3s cubic-bezier(.25, .8, .5, 1) !default$typography-font-family : 'Roboto', '-apple-system', 'Helvetica Neue', Helvetica, Arial, sans-serif !default$min-line-height : 1.12 !default
$button-border-radius : 3px !default$button-padding : 4px 16px !default$button-dense-padding : .285em !default$button-transition : $generic-hover-transition !default$button-font-size : 14px !default$button-line-height : 1.715em !default$button-font-weight : 500 !default$button-shadow : $shadow-2 !default$button-shadow-active : $shadow-5 !default$button-rounded-border-radius : 28px !default$button-push-border-radius : 7px !default
$chat-message-received-color : #000 !default$chat-message-received-bg : $green-4 !default$chat-message-sent-color : #000 !default$chat-message-sent-bg : $grey-4 !default$chat-message-avatar-size : 48px !default$chat-message-border-radius : $generic-border-radius !default$chat-message-distance : 8px !default$chat-message-text-padding : 8px !default
$item-base-color : $grey-5 !default
$editor-border-color : $separator-color !default$editor-border-dark-color : $separator-dark-color !default$editor-content-padding : 10px !default$editor-content-min-height : 10em !default$editor-toolbar-padding : 4px !default$editor-hr-color : $editor-border-color !default$editor-hr-dark-color : $editor-border-dark-color !default$editor-button-gutter : 4px !default
$table-transition : $generic-hover-transition !default$table-border-radius : $generic-border-radius !default$table-box-shadow : $shadow-2 !default
4.3.3 Sass Variables
- 49/67 - Copyright © 2020 - 2021 <a href="https://alinex.de">Alexander Schilling</a>
$table-border-color : $separator-color !default$table-hover-background : rgba(0, 0, 0, .03) !default$table-selected-background : rgba(0, 0, 0, .06) !default
$table-dark-border-color : $separator-dark-color !default$table-dark-hover-background : rgba(255, 255, 255, .07) !default$table-dark-selected-background : rgba(255, 255, 255, .1) !default
$toolbar-min-height : 50px !default$toolbar-padding : 0 12px !default$toolbar-inset-size : 58px !default$toolbar-title-font-size : 21px !default$toolbar-title-font-weight : normal !default$toolbar-title-letter-spacing : .01em !default$toolbar-title-padding : 0 12px !default
$layout-border : 1px solid $separator-color !default$layout-shadow : 0 0 10px 2px rgba(0,0,0,0.2), 0 0px 10px rgba(0,0,0,0.24) !default
$menu-background : #fff !default$menu-box-shadow : $shadow-2 !default$menu-max-width : 95vw !default
$rating-grade-color : $yellow !default$rating-shadow : 0 1px 3px rgba(0, 0, 0, .12), 0 1px 2px rgba(0, 0, 0, .24) !default
$tooltip-color : #fafafa !default$tooltip-background : $grey-7 !default$tooltip-padding : 6px 10px !default$tooltip-border-radius : $generic-border-radius !default$tooltip-fontsize : 10px !default$tooltip-mobile-padding : 8px 16px !default$tooltip-mobile-fontsize : 14px !default
$option-focus-transition : .22s cubic-bezier(0,0,.2,1) !default
$input-font-size : 14px !default$input-text-color : rgba(0,0,0,.87) !default$input-label-color : rgba(0,0,0,.6) !default$input-autofill-color : inherit !default
$img-width : 100% !default$img-background-repeat : no-repeat !default$img-loading-font-size : 50px !default$img-content-position : absolute !default$img-content-padding : 16px !default$img-content-color : #fff !default$img-content-background : rgba(0, 0, 0, .47) !default
4.3.3 Sass Variables
- 50/67 - Copyright © 2020 - 2021 <a href="https://alinex.de">Alexander Schilling</a>
4.4 InternationalizationTo make the application multi lingual it uses internationalization through the vue-i18n module.
For each language specific files exists containing the translated phrases based on structured keys. Quasar components already
include translations which will be used automatically and have not to be translated again.
4.4.1 File Structure
Under src/i18n each language is added with it's translations. The values are split into logical groups which are located in individual
files for better readability. They all are put together in the index.js files:
All the JavaScript files listed above will evaluate to a big translation object.
But it is also possible to put some local translations, which are only for the specific page or component, within the '.vue' file. But this
should not be done because it will later not be updated and forgotten for new languages.
4.4.2 Use of translations
The general $t (translate) function registered in Vue is used therefore. To specify the translation element we use the above defined
structure with it's readable short path names to access in templates:
mykey1 in HTML body
mykey2 in attribute
mykey3 programmatically
Variables
Variables may be used in different forms. As named parameters by using a data object:
Or with an data array:
src/└── i18n/ ├── index,js # loading all languages ├── en-US/ │ ├── index.js # combining english translations │ ├── layout.js # layout translations │ └── core.js # core group translation └── de/ ├── index.js # combining german translations ├── layout.js # layout translations └── core.js # core group translation
<template> <q-page> <q-btn :label="$t('mykey2')"> {{ $t('mykey1') }} <span v-html="content"></span> </q-page></template>
<script>export default { data() { return { content: this.$t('mykey3') } }}</script>
•
•
•
// Messageconst messages = { en: { hello: "{msg} world" } };// Template<p>{{ $t('hello', { msg: 'hello' }) }}</p>// Output<p>hello world</p>
4.4 Internationalization
- 51/67 - Copyright © 2020 - 2021 <a href="https://alinex.de">Alexander Schilling</a>
HTML Content
This should only be used in limited form, because it can destroy layouts:
Pluralization
You can translate with pluralization. You must define the locale that have a pipe | separator, and define plurals in pipe separator.
Your template will need to use $tc() instead of $t() .
You don't need to explicitly give the number for pluralization. The number can be accessed within locale messages via pre-defined
named arguments {count} and/or {n} .
Date
// Messageconst messages = { en: { message: { hello: '{0} world' } } }// Template<p>{{ $t('message.hello', ['hello']) }}</p>// Output<p>hello world</p>
// Messageconst messages = { en: { message: { hello: 'hello <br> world' } } }// Template<p v-html="$t('message.hello')"></p>// Output<p>hello <br> world</p>
// Messageconst messages = { en: { car: 'car | cars', apple: 'no apples | one apple | {count} apples' }}// Template<p>{{ $tc('car', 1) }}</p><p>{{ $tc('car', 2) }}</p><p>{{ $tc('apple', 0) }}</p><p>{{ $tc('apple', 1) }}</p><p>{{ $tc('apple', 10, { count: 10 }) }}</p>// Output<p>car</p><p>cars</p><p>no apples</p><p>one apple</p><p>10 apples</p>
// Messageconst messages = { en: { apple: 'no apples | one apple | {count} apples', banana: 'no bananas | {n} banana | {n} bananas' }}// Template<p>{{ $tc('apple', 10, { count: 10 }) }}</p><p>{{ $tc('apple', 10) }}</p><p>{{ $tc('banana', 1, { n: 1 }) }}</p><p>{{ $tc('banana', 1) }}</p><p>{{ $tc('banana', 100, { n: 'too many' }) }}</p>// Output<p>10 apples</p><p>10 apples</p><p>1 banana</p><p>1 banana</p><p>too many bananas</p>
<!-- Template --><p>{{ $d(new Date(), 'short') }}</p><p>{{ $d(new Date(), 'long', 'ja-JP') }}</p><!-- Output --><p>Apr 19, 2017</p><p>2017年4月19日(水) 午前2:19</p>
4.4.2 Use of translations
- 52/67 - Copyright © 2020 - 2021 <a href="https://alinex.de">Alexander Schilling</a>
Number
Linked locale messages
If there's a translation key that will always have the same concrete text as another one you can just link to it. To link to another
translation key, all you have to do is to prefix its contents with an @: sign followed by the full name of the translation key including
the namespace you want to link to.
4.4.3 Management
With the help of the VSCode Plugin i18n-ally it is easy to work with translations. It lets you:
show translation values in code
show all translations as hover
calculate translation progress
show missing or unused translations
extract text into i18n files
An other possibility is to use BabelEdit which is a downloadable standalone application which can load the json files and translate
them within it's dialogs. This allows to give the translation files to external translators. Afterwards the translated files has to be
imported into the code.
The base configuration is done in the boot/i18n.js . Here you may also change the base language used, if the translated is not
available.
Add new language
To add a new language copy an existing language folder within src/i18n/... and change it's text values.
You also have to add it within src/i18n/index.ts to be exported like en-us or the others.
<!-- Template --><p>{{ $n(100, 'currency') }}</p><p>{{ $n(100, 'currency', 'ja-JP') }}</p><!-- Output --><p>$100.00</p><p>¥100</p>
const messages = { en: { message: { the_world: "the world", dio: "DIO:", linked: "@:message.dio @:message.the_world !!!!" } }};
•
•
•
•
•
4.4.3 Management
- 53/67 - Copyright © 2020 - 2021 <a href="https://alinex.de">Alexander Schilling</a>
4.5 Special ComponentsThe following special Alinex components are contained within the core.
4.5.1 Global
Notify
To give some response messages to the user the quasar notify object is used. This can be done within a vue file:
Or outside of a vue file (as negative message):
The plugin is predefined to display positive messages on the bottom of the page, negative ones directly in the center of the screen. An
additional logging to console should always be made. For debugging this makes it possible to see what was done last.
4.5.2 Dialogs
Dialog (ax-dialog)
This is the base setup to make all the dialogs the same. It contains the card layout with a titlebar with title and icon
properties. The content can be set with the default slot and the model property is used for the visibility state.
About Dialog (ax-about)
An implementation of a dialog with the about section containing information from package.json and the policy from the
translation properties under group about.* .
Login Dialog (ax-login)
An implementation of a dialog with the login form for local authentication complete with the login procedure. This maybe
extended later to also support missing password or other authentication types.
// log the error to the browser console using log(), warn() or error()console.log(message)// show the visible elementthis.$q.notify({ type: 'positive', message})
// load the pluginimport { Notify } from 'quasar'
// log the error to the browser console using log(), warn() or error()console.warn(message)// show the visible elementNotify.create({ type: 'negative', // or positive message: this.$t('layout.failed'), caption: message // caption will add a second line for a sub message})
•
•
•
4.5 Special Components
- 54/67 - Copyright © 2020 - 2021 <a href="https://alinex.de">Alexander Schilling</a>
4.5.3 Module Organization
The following components are used to include modules into the layout in sidebar and as dashboard icon.
Sidebar Extend (ax-sidebar-extend)
Placeholder used to be overwritten in parent project to add more menu entries using the following link component.
Sidebar Link (ax-sidebar-link)
Entry in the sidebar configured using properties: title , caption , icon , link , service .
Index Extend (ax-index-extend)
Placeholder used to be overwritten in parent project to add modules to dashboard using the following icon component. This
works identical to the sidebar extension.
Index Icon (ax-index-icon)
Entry in the sidebar configured using properties: title , caption , icon , link , service .
Example
<template> <ax-dialog :model.sync="localModel" :title="xxxxxxx" icon="info"> <q-card-section class="q-pt-none"> <p>Here comes the content...</p> </q-card-actions> <!-- more card sections are possible --> </ax-dialog></template>
<script>import axDialog from 'components/dialog'
export default { name: 'MyDialog',
props: { model: { type: Boolean, required: true } },
components: { axDialog },
data () { // locally used data },
computed: { // used to keep model in sync localModel: { get () { return this.model }, set (localModel) { this.$emit('update:model', localModel) } } }}</script>
•
•
•
•
4.5.3 Module Organization
- 55/67 - Copyright © 2020 - 2021 <a href="https://alinex.de">Alexander Schilling</a>
Example
<template> <div class="icons row"> <ax-index-icon v-for="link in links" :key="link.title" v-bind="link" /> </div></template>
<script>import axIndexIcon from 'components/index-icon'
const list = vue => { return [ { title: vue.$t('mymodule.fn1.title'), caption: vue.$t('mymodule.fn1.subtitle'), icon: 'mdi-movie-search', link: '/mymodule/fn1' }, { title: vue.$t('mymodule.fn2.title'), caption: vue.$t('mymodule.fn2.subtitle'), icon: 'mdi-table-large', link: '/mymodule/fn2' } ]}
export default { name: 'indexExtend',
components: { axIndexIcon },
computed: { links () { return list(this) } }}</script>
4.5.3 Module Organization
- 56/67 - Copyright © 2020 - 2021 <a href="https://alinex.de">Alexander Schilling</a>
4.6 Object Create, Read, Update and DeleteThis shows on the example of the user object how to administrate it in the GUI.
To do this you need:
routing
store
pages
translations
See below how to do it.
4.6.1 Routing
4.6.2 Store
Feathers-vuex focuses on abstracting away the Vuex syntax in favor of using Model classes
4.6.3 Pages
4.6.4 Security
The authorization works based on abilities. They will be checked in multiple places if the abilities which a user has through his roles
matches the abilities needed to access:
Within the router the meta.ability setting contains the right needed to access this route ( [action, subject, field] ).
In the page itself using the v-if="$can('read', service) attribute
This will be checked through the boot/casl.js plugin in the router loading of abilities will be done with the login.
4.6.5 Translations
•
•
•
•
1.
2.
4.6 Object Create, Read, Update and Delete
- 57/67 - Copyright © 2020 - 2021 <a href="https://alinex.de">Alexander Schilling</a>
4.7 Store
4.7 Store
- 58/67 - Copyright © 2020 - 2021 <a href="https://alinex.de">Alexander Schilling</a>
5. Development
5.1 DevelopmentThis section will further describe how to create your specific application with this framework.
5. Development
- 59/67 - Copyright © 2020 - 2021 <a href="https://alinex.de">Alexander Schilling</a>
5.2 Parent ProjectThe following pages will explain how your specific own system is build with the Alinex GUI as a base.
5.2 Parent Project
- 60/67 - Copyright © 2020 - 2021 <a href="https://alinex.de">Alexander Schilling</a>
5.2.1 Setup
To make your own project you need the following files:
5.2.1 Setup
- 61/67 - Copyright © 2020 - 2021 <a href="https://alinex.de">Alexander Schilling</a>
package.json
.eslint.rc
{ "name": "my-gui", "title": "My GUI", "version": "0.1.0", "build": "SNAPSHOT", "description": "Human interface to my server with it's applications.", "productName": "My-GUI","cordovaId": "de.alinex.my-gui",
"capacitorId": "", "responsible": "Alexander Schilling", "foreignData": "", "private": true, "author": "Alexander Schilling", "scripts": { "dev": "bin/gui-dev", "lint": "eslint --ext .js,.vue src --fix --ignore-path .gitignore ./", "test": "echo \"No test specified\" && exit 0", "play": "quasar dev -m cordova -T android", "build": "rm -r dist/* && npm run build-spa && npm run build-pwa && npm run build-linux && npm run build-darwin && npm run build-win32 && npm run build-android", "build-spa": "quasar build && tar -C dist -czf dist/Alinex-GUI_online-spa.tgz spa", "build-pwa": "quasar build -m pwa && tar -C dist -czf dist/Alinex-GUI_online-pwa.tgz pwa", "build-ssr": "quasar build -m ssr", "build-linux": "quasar build -m electron -T linux && tar -C dist/electron -czf dist/Alinex-GUI_linux-x64.tgz Alinex-GUI-linux-x64 >/dev/null", "build-darwin": "quasar build -m electron -T darwin && tar -C dist/electron -czf dist/Alinex-GUI_darwin-x64.tgz Alinex-GUI-darwin-x64 >/dev/null", "build-win32": "quasar build -m electron -T win32 && cd dist/electron && zip -r ../Alinex-GUI_win32-x64.zip Alinex-GUI-win32-x64 >/dev/null && cd ../..", "build-android": "ANDROID_HOME=~/Android/Sdk quasar build -m capacitor -T android && cp dist/capacitor/android/apk/release/app-release-unsigned.apk dist/Alinex-GUI_android.apk", "build-ios": "test \"$OSTYPE\" = \"darwin\" && ( quasar build -m capacitor -T ios ) || echo 'ios build is only possible on MacOS with Xcode installed.'", "pdf": "./mkdocs-pdf.sh", "preversion": "npm test && npm run pdf && git add -A && git commit -m \"Update PDF documentation\"", "postpublish": "git push origin --all && git push origin --tags" }, "dependencies": { "@alinex/gui": "^0.1.0", "@feathersjs/authentication-client": "^4.5.2", "@feathersjs/feathers": "^4.5.2", "@feathersjs/rest-client": "^4.5.2", "@feathersjs/socketio-client": "^4.5.2", "@quasar/extras": "^1.0.0", "@vue/composition-api": "^0.5.0", "axios": "^0.18.1", "feathers-hooks-common": "^5.0.2", "feathers-vuex": "^3.9.1", "flag-icon-css": "^3.4.6", "quasar": "^1.0.0", "socket.io-client": "^2.3.0", "vue-i18n": "^8.0.0", "vuelidate": "^0.7.5" }, "devDependencies": { "@capacitor/ios": "^1.5.2", "@quasar/app": "^1.0.0", "@quasar/quasar-app-extension-icon-genie": "^1.1.3", "babel-eslint": "^10.0.1", "devtron": "^1.4.0", "electron": "^8.2.0", "electron-debug": "^3.0.1", "electron-devtools-installer": "^2.2.4", "electron-packager": "^14.2.1", "eslint": "^6.8.0", "eslint-config-airbnb-base": "^14.0.0", "eslint-config-standard": "^14.1.1", "eslint-loader": "^3.0.3", "eslint-plugin-import": "^2.20.1", "eslint-plugin-node": "^11.0.0", "eslint-plugin-promise": "^4.2.1", "eslint-plugin-standard": "^4.0.1", "eslint-plugin-vue": "^6.1.2" }, "engines": { "node": ">= 10.18.1", "npm": ">= 6.13.4", "yarn": ">= 1.21.1" }, "browserslist": [ "last 10 Chrome versions", "last 10 Firefox versions", "last 4 Edge versions", "last 7 Safari versions", "last 8 Android versions", "last 8 ChromeAndroid versions", "last 8 FirefoxAndroid versions", "last 10 iOS versions", "last 5 Opera versions" ], "resolutions": { "@babel/parser": "7.7.5" }}
module.exports = { root: true,
parserOptions: { parser: "babel-eslint", sourceType: "module" },
env: {
5.2.1 Setup
- 62/67 - Copyright © 2020 - 2021 <a href="https://alinex.de">Alexander Schilling</a>
Now you can setup the application:
Now you can use npm run dev or npm run build .
mkdir bincd binln -s ../node_modules/@alinex/gui/bin/gui-* .cd ..npm install
5.2.1 Setup
- 63/67 - Copyright © 2020 - 2021 <a href="https://alinex.de">Alexander Schilling</a>
5.3 Customization
5.3.1 Logo
This can be changed by overwriting the default with an image under the following position: - src/assets/logo.png -
src/assets/logo-black.png
The -black variant is used in dark mode. A transparent picture therefore may be identical.
5.3.2 Colors
The file src/css/quasar.variables.scss contains the colors used for the default names. To change the theme to your colors simple
copy this file from the Alinex GUI and change the color codes. You may use the Quasar Theme Builder to select and see the most
colors together.
5.3 Customization
- 64/67 - Copyright © 2020 - 2021 <a href="https://alinex.de">Alexander Schilling</a>
5.4 Extending Modules
5.4.1 Routing
Within the application the routing will decide which page to show. The core routes can be extended using:
5.4.2 Pages
The title will be set by the layout but can also be set here to something other:
src/router/extend.jsexport default [ { path: '/myapp', component: () => import('layouts/Default.vue'), children: [ { path: '', component: () => import('pages/myapp/index') }, { path: 'info', component: () => import('pages/myapp/info'), meta: { module: 'core.info' } } ] }]
meta () { return { title: this.$t(`${this.$route.meta.module}.title`) }}
5.4 Extending Modules
- 65/67 - Copyright © 2020 - 2021 <a href="https://alinex.de">Alexander Schilling</a>
5.4.3 Menu
To add new modules they need to be appended to the sidebar and to the start page. Therefore the following changes have to be made.
We add two menus, both using separate files to keep it more clean.
src/assets/data/my-menu.js
src/components/sidebar-extend.vue
src/components/sidebar-menu1.vue
export default vue => { return [ { title: vue.$t('core.info.title'), caption: vue.$t('core.info.subtitle'), icon: 'info', link: '/info' }, { title: vue.$t('core.users.title'), caption: vue.$t('core.users.subtitle'), icon: 'mdi-account', link: '/users', service: 'users' }, { title: vue.$t('core.roles.title'), caption: vue.$t('core.roles.subtitle'), icon: 'verified_user', link: '/roles', service: 'roles' } ]}
<template> <ax-sidebar-menu-one /> <ax-sidebar-menu-two /></template>
<script>import axSidebarMenuOne from 'components/sidebar-mymenu-one'import axSidebarMenuTwo from 'components/sidebar-mymenu-two'
export default { name: 'sidebarExtend',
components: { axSidebarMenuOne, axSidebarMenuTwo }}</script>
<template> <q-expansion-item switch-toggle-side expand-separator :label="$t('mymenu.title')"> <ax-sidebar-link v-for="link in links" :key="link.title" v-bind="link" /> </q-expansion-item></template>
<script>import axSidebarLink from 'components/sidebar-link'import myMenu from 'assets/data/my-menu'
export default { name: 'sidebarMenu1',
components: { axSidebarLink },
computed: { links () { return myMenu(this) } }}</script>
5.4.3 Menu
- 66/67 - Copyright © 2020 - 2021 <a href="https://alinex.de">Alexander Schilling</a>
5.4.4 Dashboard
And at last the additional pages can also be added to the dashboard as icons using:
src/components/index-extend.vue<template> <div class="icons row"> <ax-index-icon v-for="link in links" :key="link.title" v-bind="link" /> </div></template>
<script>import axIndexIcon from 'components/index-icon'import myMenu from 'assets/data/my-menu'
export default { name: 'indexExtend',
components: { axIndexIcon },
computed: { links () { return myMenu(this) } }}</script>
5.4.4 Dashboard
- 67/67 - Copyright © 2020 - 2021 <a href="https://alinex.de">Alexander Schilling</a>