refs #3971 import() forms, refactor, async, retrocompatibility
gitea/hedera-web/pipeline/head This commit looks good Details

This commit is contained in:
Juan Ferrer 2022-11-17 23:31:48 +01:00
parent 0c7476a37c
commit 6ecce8fec6
21 changed files with 3667 additions and 264 deletions

29
app.js
View File

@ -1,6 +1,7 @@
__webpack_public_path__ = _PUBLIC_PATH;
require('hedera/hedera');
import 'promise-polyfill/src/polyfill';
import 'hedera/hedera';
const locales = require('./import').locales;
const packageJson = require('./package.json');
@ -8,31 +9,33 @@ window.onload = function() {
loadLocale(main);
}
function main(req) {
if (req) onLocaleLoad(req);
function main() {
Vn.setVersion(packageJson.version);
hederaWeb = new Hedera.App();
const hederaWeb = new Hedera.App();
window.hederaWeb = hederaWeb;
hederaWeb.run();
}
function loadLocale(cb) {
function loadLocale(callback) {
Vn.Locale.init();
var lang = Vn.Locale.language;
var req = require.context('js', true, /locale\/en.yml$/);
onLocaleLoad(req);
onLocaleLoad(Vn.Locale.fallbackLang, req);
const fn = locales[lang];
if (fn)
fn(cb);
const loadFn = locales[lang];
if (loadFn)
loadFn(function(req) {
onLocaleLoad(lang, req);
callback();
});
else
cb();
callback();
}
function onLocaleLoad(req) {
function onLocaleLoad(lang, req) {
var keys = req.keys();
for (var i = 0; i < keys.length; i++)
Vn.Locale.add(req(keys[i]));
Vn.Locale.add(req(keys[i]), lang);
}

View File

@ -27,34 +27,34 @@ export default new Class({
this.$.oldPassword.focus();
}
,onPassModifyClick() {
try {
var oldPassword = this.$.oldPassword.value;
var newPassword = this.$.newPassword.value;
var repeatedPassword = this.$.repeatPassword.value;
,async onPassModifyClick() {
var oldPassword = this.$.oldPassword.value;
var newPassword = this.$.newPassword.value;
var repeatedPassword = this.$.repeatPassword.value;
if (newPassword == '' && repeatedPassword == '')
throw new Error(_('Passwords empty'));
if (newPassword !== repeatedPassword)
throw new Error(_('Passwords doesn\'t match'));
var verificationToken = this.hash.$.verificationToken;
var params = {newPassword: newPassword};
if (newPassword == '' && repeatedPassword == '')
throw new Error(_('Passwords empty'));
if (newPassword !== repeatedPassword)
throw new Error(_('Passwords doesn\'t match'));
var verificationToken = this.hash.$.verificationToken;
var params = {newPassword: newPassword};
if (verificationToken) {
params.verificationToken = verificationToken;
this.conn.send('user/restore-password', params,
this._onPassChange.bind(this));
} else {
if (verificationToken) {
params.verificationToken = verificationToken;
this.conn.send('user/restore-password', params,
this._onPassChange.bind(this));
} else {
try {
let userId = this.gui.user.id;
params.oldPassword = oldPassword;
this.conn.lbSend('PATCH',
await this.conn.lbSend('PATCH',
`Accounts/${userId}/changePassword`, params,
this._onPassChange.bind(this)
);
this._onPassChange();
} catch(err) {
this._onPassChange(null, err);
}
} catch (e) {
Htk.Toast.showError(e.message);
}
}

View File

@ -22,6 +22,7 @@ export default new Class({
},
onConfigureClick() {
test();
Htk.Toast.showWarning(_('RememberReconfiguringImpact'));
this.hash.setAll({form: 'ecomerce/checkout'});
},

View File

@ -1,5 +1,6 @@
import './style.scss';
export default new Class({
const Catalog = new Class({
Extends: Hedera.Form,
Template: require('./ui.xml')
@ -30,7 +31,7 @@ export default new Class({
if (localStorage.getItem('hederaView'))
this.setView(parseInt(localStorage.getItem('hederaView')));
else
this.setView(Hedera.Catalog.View.GRID);
this.setView(Catalog.View.GRID);
this.onFilterChange();
}
@ -144,19 +145,19 @@ export default new Class({
}
,setView(view) {
if (view === Hedera.Catalog.View.GRID) {
if (view === Catalog.View.GRID) {
this.$.viewButton.setProperties({
icon: 'view_list',
tip: _('List view')
});
this.view = Hedera.Catalog.View.GRID;
this.view = Catalog.View.GRID;
var className = 'grid-view';
} else {
this.$.viewButton.setProperties({
icon: 'grid_on',
tip: _('Grid view')
});
this.view = Hedera.Catalog.View.LIST;
this.view = Catalog.View.LIST;
var className = 'list-view';
}
@ -166,8 +167,8 @@ export default new Class({
}
,onSwitchViewClick() {
this.setView(this.view === Hedera.Catalog.View.LIST ?
Hedera.Catalog.View.GRID : Hedera.Catalog.View.LIST);
this.setView(this.view === Catalog.View.LIST ?
Catalog.View.GRID : Catalog.View.LIST);
}
,onItemsChange(model, status) {
@ -321,9 +322,11 @@ export default new Class({
}
});
Hedera.Catalog.extend({
Catalog.extend({
View: {
LIST: 0,
GRID: 1
}
});
export default Catalog;

View File

@ -152,7 +152,7 @@
on-click="this.onAddItemClick($event, $iter)">
<htk-image
directory="catalog"
subdir="500x500"
subdir="200x200"
form="$iter"
column="image"
stamp-column="updated"

View File

@ -64,6 +64,7 @@ export default new Class({
}
var methods = [];
let selectedMethod;
if (totalDebt <= 0) {
methods = ['balance'];

View File

@ -1,6 +1,8 @@
import './style.scss';
export default new Class({
Extends: Hedera.Form
Extends: Hedera.Form,
Template: require('./ui.xml')
,activate() {
this.$.lot.assign({

View File

@ -1,6 +1,8 @@
import './style.scss';
export default new Class({
Extends: Hedera.Form
Extends: Hedera.Form,
Template: require('./ui.xml')
,activate() {
this.$.lot.assign({

141
import.js
View File

@ -1,65 +1,86 @@
export const locales = {
'ca': cb => require([], () => cb(require.context('js', true, /locale\/ca.yml$/))),
'es': cb => require([], () => cb(require.context('js', true, /locale\/es.yml$/))),
'fr': cb => require([], () => cb(require.context('js', true, /locale\/fr.yml$/))),
'mn': cb => require([], () => cb(require.context('js', true, /locale\/mn.yml$/))),
'pt': cb => require([], () => cb(require.context('js', true, /locale\/pt.yml$/)))
ca: cb => require([],
() => cb(require.context('js', true, /locale\/ca.yml$/))),
es: cb => require([],
() => cb(require.context('js', true, /locale\/es.yml$/))),
fr: cb => require([],
() => cb(require.context('js', true, /locale\/fr.yml$/))),
mn: cb => require([],
() => cb(require.context('js', true, /locale\/mn.yml$/))),
pt: cb => require([],
() => cb(require.context('js', true, /locale\/pt.yml$/)))
};
export const formMap = {
'account/address':
() => import('account/address'),
'account/address-list':
() => import('account/address-list'),
'account/conf':
() => import('account/conf'),
'admin/access-log':
() => import('admin/access-log'),
'admin/connections':
() => import('admin/connections'),
'admin/items':
() => import('admin/items'),
'admin/links':
() => import('admin/links'),
'admin/photos':
() => import('admin/photos'),
'admin/queries':
() => import('admin/queries'),
'admin/users':
() => import('admin/users'),
'admin/visits':
() => import('admin/visits'),
'agencies/packages':
() => import('agencies/packages'),
'agencies/provinces':
() => import('agencies/provinces'),
'cms/about':
() => import('cms/about'),
'cms/contact':
() => import('cms/contact'),
'cms/home':
() => import('cms/home'),
'cms/location':
() => import('cms/location'),
'cms/why':
() => import('cms/why'),
'ecomerce/basket':
() => import('ecomerce/basket'),
'ecomerce/catalog':
() => import('ecomerce/catalog'),
'ecomerce/checkout':
() => import('ecomerce/checkout'),
'ecomerce/confirm':
() => import('ecomerce/confirm'),
'ecomerce/invoices':
() => import('ecomerce/invoices'),
'ecomerce/orders':
() => import('ecomerce/orders'),
'ecomerce/ticket':
() => import('ecomerce/ticket'),
'news/new':
() => import('news/new'),
'news/news':
() => import('news/news')
export const routes = {
account: {
address:
() => import('account/address'),
addressList:
() => import('account/address-list'),
conf:
() => import('account/conf')
},
admin: {
accessLog:
() => import('admin/access-log'),
connections:
() => import('admin/connections'),
items:
() => import('admin/items'),
links:
() => import('admin/links'),
photos:
() => import('admin/photos'),
queries:
() => import('admin/queries'),
users:
() => import('admin/users'),
visits:
() => import('admin/visits')
},
agencies: {
packages:
() => import('agencies/packages'),
provinces:
() => import('agencies/provinces')
},
cms: {
about:
() => import('cms/about'),
contact:
() => import('cms/contact'),
home:
() => import('cms/home'),
location:
() => import('cms/location'),
why:
() => import('cms/why')
},
ecomerce: {
basket:
() => import('ecomerce/basket'),
catalog:
() => import('ecomerce/catalog'),
checkout:
() => import('ecomerce/checkout'),
confirm:
() => import('ecomerce/confirm'),
invoices:
() => import('ecomerce/invoices'),
orders:
() => import('ecomerce/orders'),
ticket:
() => import('ecomerce/ticket')
},
news: {
new:
() => import('news/new'),
news:
() => import('news/news')
},
reports: {
shelves:
() => import('reports/shelves')
}
};

View File

@ -15,7 +15,9 @@ module.exports = new Class({
,initialize() {
window.onerror = this._onWindowError.bind(this);
window.onunhandledrejection = e => this._onWindowRejection(e);
window.onunload = this._onWindowUnload.bind(this);
this._hash = new Vn.Hash({window: window});
var conn = new Db.Connection();
@ -66,6 +68,12 @@ module.exports = new Class({
error.lineNumber = line;
this._notifyError(error);
}
,_onWindowRejection(event) {
const err = event.reason;
this._onConnError(null, err);
event.preventDefault();
}
,_onConnError(conn, error) {
if (error instanceof Vn.JsonException) {

View File

@ -1,7 +1,8 @@
const Module = require('./module');
const Tpl = require('./gui.xml').default;
const formMap = require('../../import').formMap;
const kebabToCamel = require('vn/string-util').kebabToCamel;
const routes = require('../../import').routes;
require('./gui.scss');
module.exports = new Class({
@ -98,12 +99,12 @@ module.exports = new Class({
this.onLogoutClick();
}
,onLogoutClick() {
this._conn.close(this._onConnClose.bind(this));
}
,_onConnClose() {
this.emit('logout');
,async onLogoutClick() {
try {
await this._conn.close();
} finally {
this.emit('logout');
}
}
,_onConnLoadChange(conn, isLoading) {
@ -369,13 +370,16 @@ module.exports = new Class({
this.choosedOption = newChoosedOption;
}
await Vn.Locale.load(`forms/${formPath}`);
let FormKlass;
try {
FormKlass = (await this.importForm(formPath)).default;
} catch (err) {
this.loaderPop();
return Htk.Toast.showError(_('Error loading form'));
console.log(err);
return Htk.Toast.showError(_('Error loading form') +`: ${err.message}`);
}
this.loaderPop();
@ -388,8 +392,13 @@ module.exports = new Class({
}
,async importForm(path) {
const fn = formMap[path];
return fn ? fn() : null;
const states = path.split('/');
let importFn = routes;
for (let i = 0; i < states.length && importFn; i++)
importFn = importFn[kebabToCamel(states[i])];
if (!importFn)
throw new Error(`Route '${path}' does not exist`);
return importFn();
}
,setForm(form) {

View File

@ -1,4 +1,4 @@
var Tpl = require('./login.xml');
var Tpl = require('./login.xml').default;
require('./login.scss');
module.exports = new Class({
@ -48,30 +48,24 @@ module.exports = new Class({
this._focusUserInput();
}
,_onSubmit() {
this._conn.open(
this.$.user.value,
this.$.pass.value,
this.$.remember.checked,
this._onConnOpen.bind(this)
);
,async _onSubmit() {
this._disableUi(true);
}
,_onConnOpen(conn, success, error) {
this.$.pass.value = '';
this._disableUi(false);
if (success) {
var user = this.$.user.value;
if (user)
localStorage.setItem('hederaLastUser', user);
try {
await this._conn.open(
this.$.user.value,
this.$.pass.value,
this.$.remember.checked
);
const user = this.$.user.value;
if (user) localStorage.setItem('hederaLastUser', user);
this.emit('login');
} else {
} catch (err) {
this._focusUserInput();
throw error;
throw err;
} finally {
this.$.pass.value = '';
this._disableUi(false);
}
}

View File

@ -43,61 +43,46 @@ module.exports = new Class({
* @param {String} user The user name
* @param {String} password The user password
* @param {Boolean} remember Specifies if the user should be remembered
* @param {Function} openCallback The function to call when operation is done
* @return {Promise} Resolved when operation is done
*/
,open(user, pass, remember, callback) {
,async open(user, pass, remember) {
let params;
if (user !== null && user !== undefined) {
var params = {
params = {
user: user
,password: pass
,remember: remember
};
} else
var params = null;
params = null;
this.lbSend('POST', 'Accounts/login', params,
this._onOpen.bind(this, callback, remember));
}
try {
const json = await this.lbSend('POST', 'Accounts/login', params);
/*
* Called when open operation is done.
*/
,_onOpen(callback, remember, json, error) {
if (json) {
this._connected = true;
this.token = json.token;
var storage = remember ? localStorage : sessionStorage;
storage.setItem('vnToken', this.token);
storage.setItem('vnToken', json.token);
this.token = json.token;
this._connected = true;
this.emit('openned');
} else
} catch(err) {
this._closeClient();
if (callback)
callback(this, this._connected, error);
throw err;
}
}
/**
* Closes the connection to the REST service.
*
* @param {Function} callback The function to call when operation is done
* @return {Promise} Resolved when operation is done
*/
,close(callback) {
this.lbSend('POST', 'Accounts/logout', null,
this._onClose.bind(this, callback));
,async close() {
await this.lbSend('POST', 'Accounts/logout');
this._closeClient();
}
/*
* Called when close operation is done.
*/
,_onClose(callback, json, error) {
this.emit('closed');
if (callback)
callback(this, null, error);
}
,_closeClient() {
this._connected = false;
@ -203,18 +188,25 @@ module.exports = new Class({
this._addRequest();
}
,lbSend(method, url, params, callback) {
,async lbSend(method, url, params) {
var request = new XMLHttpRequest();
request.open(method, `api/${url}`, true);
request.setRequestHeader('Content-Type',
'application/json;charset=utf-8');
if (this.token)
request.setRequestHeader('Authorization', this.token);
request.onreadystatechange =
this._onStateChange.bind(this, request, callback);
request.send(params && JSON.stringify(params));
this._addRequest();
return await new Promise((resolve, reject) => {
function callback(data, err) {
if (err) return reject(err);
resolve(data);
}
request.onreadystatechange =
this._onStateChange.bind(this, request, callback);
request.send(params && JSON.stringify(params));
});
}
,_addRequest() {

View File

@ -1,101 +1,97 @@
var yaml = require('js-yaml');
vnLocaleStrings = {};
const yaml = require('js-yaml');
const locales = {};
let fallbackLang = 'en';
let lang = null;
/**
* Class to manage the internationalization.
*/
module.exports =
{
language: null
,defaultLang: 'en'
,init() {
if (this.language)
return;
init() {
if (lang) return;
this.loads = {};
this.locales = locales;
this.fallbackLang = fallbackLang;
var language = this.defaultLang;
var languages = navigator.languages;
const languages = navigator.languages;
if (languages && languages.length > 0)
language = languages[0];
lang = languages[0];
else if (navigator.language)
language = navigator.language;
lang = navigator.language;
this.language = language.substr(0, 2);
}
,load(path, callback) {
lang = lang ? lang.substr(0, 2) : fallbackLang;
this.language = lang;
},
async load(path) {
this.init();
await Promise.all([
this.loadLang(path, fallbackLang),
this.loadLang(path, lang)
]);
},
var data = {
path: path,
callback: callback,
defOk: false,
orgOk: this.defaultLang === this.language
};
/**
* @returns {Promise}
*/
loadLang(path, lang) {
let langLoad = this.loads[lang];
if (!langLoad)
langLoad = this.loads[lang] = {};
data.def = this.createRequest(data, true, this.defaultLang);
if (langLoad[path])
return Promise.resolve();
langLoad[path] = true;
if (!data.orgOk)
data.org = this.createRequest(data, false, this.language);
}
,createRequest(data, isDef, lang) {
var langFile = data.path +'/locale/'+ lang +'.yml'+ Vn.getVersion();
var request = new XMLHttpRequest();
const langFile = `${path}/locale/${lang}.yml${Vn.getVersion()}`;
const request = new XMLHttpRequest();
request.open('get', langFile, true);
request.onreadystatechange =
this.onRequestReady.bind(this, request, data, isDef);
request.send();
return request;
}
,onRequestReady(request, data, isDef) {
return new Promise(
(resolve, reject) => {
request.onreadystatechange =
() => this.onRequestReady(request, resolve, reject);
request.send();
})
.then(translations => {
this.add(translations, lang);
})
.catch(err => {
langLoad[path] = false;
console.warn(err);
});
},
onRequestReady(request, resolve, reject) {
if (request.readyState != 4)
return;
if (request.status < 200 || request.status >= 400)
return reject(new Error(`HTTP ${request.status}: ${request.statusText}`));
if (isDef) {
this.loadFromRequest(request);
data.defOk = true;
} else
data.orgOk = true;
if (data.orgOk && data.defOk) {
if (data.org != null)
this.loadFromRequest(data.org)
if (data.callback)
data.callback();
}
}
,loadFromRequest(request) {
if (request.status !== 200)
return false;
resolve(yaml.safeLoad(request.responseText));
},
try {
this.add(yaml.safeLoad(request.responseText));
return true;
} catch (e) {
console.error(e);
}
add(translations, myLang) {
if (!translations) return;
myLang = myLang || lang;
let langLocales = locales[myLang];
if (!langLocales)
langLocales = locales[myLang] = {};
for (const str in translations)
langLocales[str] = translations[str];
}
}
return false
}
,loadScript(path, callback) {
this.init();
Vn.includeJs(path +'/locale/'+ this.language, callback);
}
,add(translations) {
for (var str in translations)
vnLocaleStrings[str] = translations[str];
}
function getString(stringId, lang) {
return locales[lang] ? locales[lang][stringId] : null;
}
window._ = function(stringId) {
var string = vnLocaleStrings[stringId];
return string ? string : stringId;
let string = getString(stringId, lang);
if (!string)
string = getString(stringId, fallbackLang);
return string || stringId;
}

View File

@ -237,19 +237,6 @@ Vn = module.exports = {
,define(callback) {
this.currentCallback = callback;
}
/**
* Includes an entire Javascript library including it's localized file.
*
* @param {string} libName The folder of the library
* @param {Array<string>} files Array with every library file name
*/
,includeLib(libName, files) {
Vn.Locale.loadScript('js/'+ libName +'.js');
for (var i = 0; i < files.length; i++)
this.include('js/'+ libName +'/'+ files[i]);
}
/**
* Includes a new Javascript in the current document, if the script

3376
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -8,8 +8,10 @@
"url": "https://git.verdnatura.es/hedera-web"
},
"devDependencies": {
"@babel/preset-env": "^7.20.2",
"archiver": "^5.3.1",
"assets-webpack-plugin": "^7.1.1",
"babel-loader": "^9.1.0",
"bundle-loader": "^0.5.6",
"css-loader": "^6.7.1",
"eslint": "^8.15.0",
@ -32,6 +34,7 @@
"dependencies": {
"js-yaml": "^3.12.1",
"mootools": "^1.5.2",
"promise-polyfill": "^8.2.3",
"require-yaml": "0.0.1",
"tinymce": "^5.0.0"
},

View File

@ -41,7 +41,7 @@ class HtmlService extends Service {
if (!isset($_GET['skipBrowser'])
&& isset($_SERVER['HTTP_USER_AGENT'])
&&($browser = get_browser($_SERVER['HTTP_USER_AGENT']))) {
&& ($browser = get_browser($_SERVER['HTTP_USER_AGENT']))) {
$browserVersion =(double) $browser->version;
$minVersion = $db->getValue(
'SELECT version FROM browser WHERE name = #', [$browser->browser]);

View File

@ -19,6 +19,15 @@ const baseConfig = {
module: {
rules: [
{
test: /\.m?js$/,
exclude: /(node_modules|bower_components)/,
use: {
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env']
}
}
}, {
test: /\.css$/,
use: ['style-loader', 'css-loader'],
}, {