From f7bc6a3f6d276566104cae042f91865b7a165890 Mon Sep 17 00:00:00 2001 From: Katharina Bogad Date: Fri, 18 Jun 2021 12:44:39 +0200 Subject: [PATCH] Modernize Javascript This *absolute commit monster* does the following things: - Introduce gulp to build javascript and css files. This includes moving node_modules out of the public/ folder. Use `gulp --tasks` to get a list of all tasks; however some of them are automatically generated. Use `gulp live` to watch for changes and automatically recompile what's needed. - Upgrade to yarn2 - Upgrade FullCalendar to 4.4.2 I know that 5.x is the current version, but two major version upgrades are too much right now. Also v5 would break any custom css as they renamed a bunch of classes. - Move Styles to sass (Most) global styles are now included in one sass file. This also means that we now compile our own bootstrap. - Javascript is now in strict mode Because everything is a module now, `use strict` is now in effect for all javascript files. There are probably still some parts left where implicit variable declarations were used. - grocy*.js were split up in modules. `window.Grocy` is now an instance of GrocyClass. API-wise nothing has changed (albeit some functions were added regarding Undo actions) At the Moment, this leaks a whole bunch of functions into window (that was easier than tracking those down). - FindObjectIn... style functions were removed. Array.prototype.find and Array.prototype.filter suffice. - Use babel to preprocess javascript. - Use rollup to bundle javascript. rollup bundles and tree-shakes es6 javascript bundles. It also allows to "import" css files and generate css files specific to this javascript file. This is used in viewjs scripts, for example when importing FullCalendar, to generate an associated viewcss file. - Use postcss to post-process css files. postcss uses autoprefixer to handle browser compatiblity. Internally this uses the package `browserslist`; and is currently configured to the default setting. - Minify assets when building in production `gulp publish` builds all assets in production mode, that is, the assets get minified. This includes javascript as well as css files. - css bundling concatCss is used to pull @imports of non-sass-files into one grocy.css - animate.css is now in the main bundle animate.css was used in so many places that it is now located in the main style bundle. --- .babelrc | 5 + .gitignore | 4 +- .yarnrc.yml | 4 + controllers/SystemApiController.php | 1 + gulpfile.babel.js | 193 + js/configs/datatable.js | 113 + js/configs/globalstate.js | 497 + js/configs/lazy.js | 9 + js/configs/permissions.js | 11 + js/configs/timeago.js | 48 + js/grocy.js | 270 + js/helpers/clock.js | 51 + js/helpers/embeds.js | 20 + {public/js => js/helpers}/extensions.js | 102 +- js/helpers/frontend.js | 126 + js/helpers/global.js | 23 + js/helpers/input.js | 12 + js/helpers/messagebag.js | 9 + js/helpers/numberdisplay.js | 76 + js/helpers/qrcode.js | 21 + js/helpers/string.js | 15 + js/lib/UISound.js | 35 + js/lib/WakeLock.js | 87 + js/lib/api.js | 235 + js/lib/nightmode.js | 141 + js/vendor.js | 20 + {public => js}/viewjs/about.js | 0 .../viewjs/barcodescannertesting.js | 0 {public => js}/viewjs/batteries.js | 0 {public => js}/viewjs/batteriesjournal.js | 0 {public => js}/viewjs/batteriesoverview.js | 0 {public => js}/viewjs/batteriessettings.js | 0 {public => js}/viewjs/batteryform.js | 4 +- {public => js}/viewjs/batterytracking.js | 16 +- js/viewjs/calendar.js | 90 + {public => js}/viewjs/choreform.js | 0 {public => js}/viewjs/chores.js | 0 {public => js}/viewjs/choresjournal.js | 0 {public => js}/viewjs/choresoverview.js | 0 {public => js}/viewjs/choressettings.js | 0 {public => js}/viewjs/choretracking.js | 16 +- .../viewjs/components/barcodescanner.js | 39 +- .../viewjs/components/batterycard.js | 0 .../viewjs/components/calendarcard.js | 0 {public => js}/viewjs/components/chorecard.js | 0 .../viewjs/components/datetimepicker.js | 2 +- .../viewjs/components/datetimepicker2.js | 0 .../viewjs/components/locationpicker.js | 2 +- .../viewjs/components/numberpicker.js | 0 .../viewjs/components/productamountpicker.js | 7 +- .../viewjs/components/productcard.js | 2 + .../viewjs/components/productpicker.js | 0 .../viewjs/components/recipepicker.js | 2 +- .../components/shoppinglocationpicker.js | 2 +- .../viewjs/components/userfieldsform.js | 0 .../viewjs/components/userpicker.js | 0 {public => js}/viewjs/consume.js | 41 +- {public => js}/viewjs/equipment.js | 4 +- {public => js}/viewjs/equipmentform.js | 0 {public => js}/viewjs/inventory.js | 34 +- {public => js}/viewjs/locationcontentsheet.js | 0 {public => js}/viewjs/locationform.js | 4 +- {public => js}/viewjs/locations.js | 0 {public => js}/viewjs/login.js | 0 {public => js}/viewjs/manageapikeys.js | 0 {public => js}/viewjs/mealplan.js | 117 +- {public => js}/viewjs/openapiui.js | 0 {public => js}/viewjs/productbarcodeform.js | 4 +- {public => js}/viewjs/productform.js | 4 +- {public => js}/viewjs/productgroupform.js | 4 +- {public => js}/viewjs/productgroups.js | 0 {public => js}/viewjs/products.js | 0 {public => js}/viewjs/purchase.js | 60 +- .../viewjs/quantityunitconversionform.js | 4 +- {public => js}/viewjs/quantityunitform.js | 4 +- .../viewjs/quantityunitpluraltesting.js | 0 {public => js}/viewjs/quantityunits.js | 0 {public => js}/viewjs/recipeform.js | 4 +- {public => js}/viewjs/recipeposform.js | 3 +- {public => js}/viewjs/recipes.js | 0 js/viewjs/recipessettings.js | 8 + {public => js}/viewjs/shoppinglist.js | 7 +- {public => js}/viewjs/shoppinglistform.js | 4 +- {public => js}/viewjs/shoppinglistitemform.js | 4 +- {public => js}/viewjs/shoppinglistsettings.js | 4 +- {public => js}/viewjs/shoppinglocationform.js | 4 +- {public => js}/viewjs/shoppinglocations.js | 0 {public => js}/viewjs/stockentries.js | 23 +- {public => js}/viewjs/stockentryform.js | 6 +- {public => js}/viewjs/stockjournal.js | 0 {public => js}/viewjs/stockjournalsummary.js | 0 {public => js}/viewjs/stockoverview.js | 6 +- {public => js}/viewjs/stocksettings.js | 4 +- {public => js}/viewjs/taskcategories.js | 0 {public => js}/viewjs/taskcategoryform.js | 4 +- {public => js}/viewjs/taskform.js | 4 +- {public => js}/viewjs/tasks.js | 0 {public => js}/viewjs/taskssettings.js | 0 {public => js}/viewjs/transfer.js | 35 +- {public => js}/viewjs/userentities.js | 0 {public => js}/viewjs/userentityform.js | 4 +- {public => js}/viewjs/userfieldform.js | 4 +- {public => js}/viewjs/userfields.js | 0 {public => js}/viewjs/userform.js | 0 {public => js}/viewjs/userobjectform.js | 4 +- {public => js}/viewjs/userobjects.js | 0 {public => js}/viewjs/userpermissions.js | 0 {public => js}/viewjs/users.js | 0 {public => js}/viewjs/usersettings.js | 0 localization/strings.pot | 9 + package.json | 42 +- postcss.config.js | 18 + public/.gitignore | 4 + public/js/grocy.js | 1127 -- public/js/grocy_clock.js | 47 - public/js/grocy_dbchangedhandling.js | 65 - public/js/grocy_nightmode.js | 123 - public/js/grocy_uisound.js | 26 - public/js/grocy_wakelockhandling.js | 72 - public/viewjs/calendar.js | 59 - public/viewjs/recipessettings.js | 6 - scss/_barcodes.scss | 14 + public/css/grocy.css => scss/_main.scss | 372 +- scss/_variables.scss | 4 + scss/customizations/_bootstrap.scss | 46 + scss/customizations/_bs-combobox.scss | 4 + scss/customizations/_datatables.scss | 24 + scss/customizations/_fontawesome.scss | 5 + scss/customizations/_popperjs.scss | 5 + scss/customizations/_quagga2.scss | 7 + scss/customizations/_sbadmin2.scss | 59 + scss/customizations/_tempusdominus.scss | 4 + scss/customizations/_toastr.scss | 17 + scss/grocy.scss | 60 + .../night-mode.scss | 0 views/barcodescannertesting.blade.php | 5 - views/batteriesoverview.blade.php | 5 - views/batteryform.blade.php | 4 +- views/calendar.blade.php | 11 +- views/choreform.blade.php | 4 +- views/choresoverview.blade.php | 5 - views/components/barcodescanner.blade.php | 1 - views/components/productcard.blade.php | 1 - views/consume.blade.php | 10 +- views/equipmentform.blade.php | 6 +- views/inventory.blade.php | 6 +- views/layout/default.blade.php | 128 +- views/locationform.blade.php | 4 +- views/mealplan.blade.php | 15 +- views/productbarcodeform.blade.php | 12 +- views/productform.blade.php | 6 +- views/productgroupform.blade.php | 4 +- views/purchase.blade.php | 10 +- views/quantityunitconversionform.blade.php | 4 +- views/quantityunitform.blade.php | 4 +- views/recipeform.blade.php | 10 +- views/recipeposform.blade.php | 2 +- views/recipes.blade.php | 4 +- views/shoppinglist.blade.php | 6 - views/shoppinglistform.blade.php | 4 +- views/shoppinglistitemform.blade.php | 8 +- views/shoppinglocationform.blade.php | 4 +- views/stockentries.blade.php | 5 - views/stockentryform.blade.php | 4 +- views/stockoverview.blade.php | 2 +- views/taskcategoryform.blade.php | 4 +- views/taskform.blade.php | 4 +- views/tasks.blade.php | 5 - views/transfer.blade.php | 4 +- views/userentityform.blade.php | 4 +- views/userfieldform.blade.php | 4 +- views/userform.blade.php | 6 +- views/userobjectform.blade.php | 8 +- views/userpermissions.blade.php | 2 +- yarn.lock | 14163 ++++++++++++---- 175 files changed, 13579 insertions(+), 5860 deletions(-) create mode 100644 .babelrc create mode 100644 .yarnrc.yml create mode 100644 gulpfile.babel.js create mode 100644 js/configs/datatable.js create mode 100644 js/configs/globalstate.js create mode 100644 js/configs/lazy.js create mode 100644 js/configs/permissions.js create mode 100644 js/configs/timeago.js create mode 100644 js/grocy.js create mode 100644 js/helpers/clock.js create mode 100644 js/helpers/embeds.js rename {public/js => js/helpers}/extensions.js (61%) create mode 100644 js/helpers/frontend.js create mode 100644 js/helpers/global.js create mode 100644 js/helpers/input.js create mode 100644 js/helpers/messagebag.js create mode 100644 js/helpers/numberdisplay.js create mode 100644 js/helpers/qrcode.js create mode 100644 js/helpers/string.js create mode 100644 js/lib/UISound.js create mode 100644 js/lib/WakeLock.js create mode 100644 js/lib/api.js create mode 100644 js/lib/nightmode.js create mode 100644 js/vendor.js rename {public => js}/viewjs/about.js (100%) rename {public => js}/viewjs/barcodescannertesting.js (100%) rename {public => js}/viewjs/batteries.js (100%) rename {public => js}/viewjs/batteriesjournal.js (100%) rename {public => js}/viewjs/batteriesoverview.js (100%) rename {public => js}/viewjs/batteriessettings.js (100%) rename {public => js}/viewjs/batteryform.js (94%) rename {public => js}/viewjs/batterytracking.js (88%) create mode 100644 js/viewjs/calendar.js rename {public => js}/viewjs/choreform.js (100%) rename {public => js}/viewjs/chores.js (100%) rename {public => js}/viewjs/choresjournal.js (100%) rename {public => js}/viewjs/choresoverview.js (100%) rename {public => js}/viewjs/choressettings.js (100%) rename {public => js}/viewjs/choretracking.js (89%) rename {public => js}/viewjs/components/barcodescanner.js (90%) rename {public => js}/viewjs/components/batterycard.js (100%) rename {public => js}/viewjs/components/calendarcard.js (100%) rename {public => js}/viewjs/components/chorecard.js (100%) rename {public => js}/viewjs/components/datetimepicker.js (99%) rename {public => js}/viewjs/components/datetimepicker2.js (100%) rename {public => js}/viewjs/components/locationpicker.js (95%) rename {public => js}/viewjs/components/numberpicker.js (100%) rename {public => js}/viewjs/components/productamountpicker.js (89%) rename {public => js}/viewjs/components/productcard.js (99%) rename {public => js}/viewjs/components/productpicker.js (100%) rename {public => js}/viewjs/components/recipepicker.js (95%) rename {public => js}/viewjs/components/shoppinglocationpicker.js (95%) rename {public => js}/viewjs/components/userfieldsform.js (100%) rename {public => js}/viewjs/components/userpicker.js (100%) rename {public => js}/viewjs/consume.js (94%) rename {public => js}/viewjs/equipment.js (97%) rename {public => js}/viewjs/equipmentform.js (100%) rename {public => js}/viewjs/inventory.js (94%) rename {public => js}/viewjs/locationcontentsheet.js (100%) rename {public => js}/viewjs/locationform.js (94%) rename {public => js}/viewjs/locations.js (100%) rename {public => js}/viewjs/login.js (100%) rename {public => js}/viewjs/manageapikeys.js (100%) rename {public => js}/viewjs/mealplan.js (89%) rename {public => js}/viewjs/openapiui.js (100%) rename {public => js}/viewjs/productbarcodeform.js (96%) rename {public => js}/viewjs/productform.js (99%) rename {public => js}/viewjs/productgroupform.js (93%) rename {public => js}/viewjs/productgroups.js (100%) rename {public => js}/viewjs/products.js (100%) rename {public => js}/viewjs/purchase.js (93%) rename {public => js}/viewjs/quantityunitconversionform.js (98%) rename {public => js}/viewjs/quantityunitform.js (97%) rename {public => js}/viewjs/quantityunitpluraltesting.js (100%) rename {public => js}/viewjs/quantityunits.js (100%) rename {public => js}/viewjs/recipeform.js (98%) rename {public => js}/viewjs/recipeposform.js (97%) rename {public => js}/viewjs/recipes.js (100%) create mode 100644 js/viewjs/recipessettings.js rename {public => js}/viewjs/shoppinglist.js (98%) rename {public => js}/viewjs/shoppinglistform.js (94%) rename {public => js}/viewjs/shoppinglistitemform.js (98%) rename {public => js}/viewjs/shoppinglistsettings.js (62%) rename {public => js}/viewjs/shoppinglocationform.js (94%) rename {public => js}/viewjs/shoppinglocations.js (100%) rename {public => js}/viewjs/stockentries.js (92%) rename {public => js}/viewjs/stockentryform.js (92%) rename {public => js}/viewjs/stockjournal.js (100%) rename {public => js}/viewjs/stockjournalsummary.js (100%) rename {public => js}/viewjs/stockoverview.js (96%) rename {public => js}/viewjs/stocksettings.js (92%) rename {public => js}/viewjs/taskcategories.js (100%) rename {public => js}/viewjs/taskcategoryform.js (94%) rename {public => js}/viewjs/taskform.js (95%) rename {public => js}/viewjs/tasks.js (100%) rename {public => js}/viewjs/taskssettings.js (100%) rename {public => js}/viewjs/transfer.js (94%) rename {public => js}/viewjs/userentities.js (100%) rename {public => js}/viewjs/userentityform.js (95%) rename {public => js}/viewjs/userfieldform.js (95%) rename {public => js}/viewjs/userfields.js (100%) rename {public => js}/viewjs/userform.js (100%) rename {public => js}/viewjs/userobjectform.js (93%) rename {public => js}/viewjs/userobjects.js (100%) rename {public => js}/viewjs/userpermissions.js (100%) rename {public => js}/viewjs/users.js (100%) rename {public => js}/viewjs/usersettings.js (100%) create mode 100644 postcss.config.js create mode 100644 public/.gitignore delete mode 100644 public/js/grocy.js delete mode 100644 public/js/grocy_clock.js delete mode 100644 public/js/grocy_dbchangedhandling.js delete mode 100644 public/js/grocy_nightmode.js delete mode 100644 public/js/grocy_uisound.js delete mode 100644 public/js/grocy_wakelockhandling.js delete mode 100644 public/viewjs/calendar.js delete mode 100644 public/viewjs/recipessettings.js create mode 100644 scss/_barcodes.scss rename public/css/grocy.css => scss/_main.scss (50%) mode change 100755 => 100644 create mode 100644 scss/_variables.scss create mode 100644 scss/customizations/_bootstrap.scss create mode 100644 scss/customizations/_bs-combobox.scss create mode 100644 scss/customizations/_datatables.scss create mode 100644 scss/customizations/_fontawesome.scss create mode 100644 scss/customizations/_popperjs.scss create mode 100644 scss/customizations/_quagga2.scss create mode 100644 scss/customizations/_sbadmin2.scss create mode 100644 scss/customizations/_tempusdominus.scss create mode 100644 scss/customizations/_toastr.scss create mode 100755 scss/grocy.scss rename public/css/grocy_night_mode.css => scss/night-mode.scss (100%) diff --git a/.babelrc b/.babelrc new file mode 100644 index 00000000..aceae784 --- /dev/null +++ b/.babelrc @@ -0,0 +1,5 @@ +{ + "presets": [ + "es2015" + ] +} \ No newline at end of file diff --git a/.gitignore b/.gitignore index 9946de1e..2d1bb9d5 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,6 @@ /public/node_modules +node_modules +.yarn /vendor /.release -embedded.txt \ No newline at end of file +embedded.txt diff --git a/.yarnrc.yml b/.yarnrc.yml new file mode 100644 index 00000000..5ef68935 --- /dev/null +++ b/.yarnrc.yml @@ -0,0 +1,4 @@ +yarnPath: ".yarn/releases/yarn-berry.cjs" +nodeLinker: node-modules +# we explicitly do not set the old files! +# js, css delivery and bundling will be done by webpack. \ No newline at end of file diff --git a/controllers/SystemApiController.php b/controllers/SystemApiController.php index 5ff7a187..0f13d16f 100644 --- a/controllers/SystemApiController.php +++ b/controllers/SystemApiController.php @@ -76,6 +76,7 @@ class SystemApiController extends BaseApiController $requestBody = $this->GetParsedAndFilteredRequestBody($request); $this->getLocalizationService()->CheckAndAddMissingTranslationToPot($requestBody['text']); + file_put_contents("php://stderr", print_r($requestBody['text'], true)); return $this->EmptyApiResponse($response); } catch (\Exception $ex) diff --git a/gulpfile.babel.js b/gulpfile.babel.js new file mode 100644 index 00000000..e834aa2d --- /dev/null +++ b/gulpfile.babel.js @@ -0,0 +1,193 @@ +'use strict'; + +import { series, parallel, dest, src, watch, task } from 'gulp'; +import rollup from '@rollup/stream'; +import sourcemaps from 'gulp-sourcemaps'; +import source from 'vinyl-source-stream'; +import buffer from 'vinyl-buffer'; +import commonjs from '@rollup/plugin-commonjs'; +import resolve from '@rollup/plugin-node-resolve'; +import rollupCss from 'rollup-plugin-css-porter'; +import gulpif from 'gulp-if'; +import uglify from 'gulp-uglify'; +import gulpsass from 'gulp-dart-sass'; // TODO: move to gulp-sass once they removed the node-sass depenency +import postcss from 'gulp-postcss'; +import glob from 'glob'; +import path from 'path'; + +// css post-processing +import cssnano from 'cssnano'; +import autoprefixer from 'autoprefixer'; +import concatCss from 'gulp-concat-css'; + +var minify = false; + +var postcss_plugins = [ + // always add autoprefixer + autoprefixer(), +]; + + +// viewjs handling +var files = glob.sync('./js/viewjs/*.js'); +var components = glob.sync('./js/viewjs/components/*.js'); + +var viewJStasks = []; + +files.forEach(function(target) +{ + task(target, cb => rollup({ + input: target, + output: { + format: 'umd', + name: path.basename(target), + sourcemap: 'inline', + }, + plugins: [resolve(), rollupCss({ + dest: './public/css/viewcss/' + path.basename(target).replace(".js", ".css") + }), commonjs()], + + }) + .pipe(source(path.basename(target), "./js/viewjs")) + .pipe(gulpif(minify, uglify())) + .pipe(buffer()) + .pipe(sourcemaps.init({ loadMaps: true })) + .pipe(sourcemaps.write('.')) + .pipe(dest('./public/viewjs'))); + viewJStasks.push(target); +}); +components.forEach(function(target) +{ + task(target, cb => rollup({ + input: target, + output: { + format: 'umd', + name: path.basename(target), + sourcemap: 'inline', + }, + plugins: [resolve(), rollupCss({ + dest: './public/css/components/' + path.basename(target).replace(".js", ".css") + }), commonjs()], + + }) + .pipe(source(path.basename(target), "./js/viewjs/components")) + .pipe(gulpif(minify, uglify())) + .pipe(buffer()) + .pipe(sourcemaps.init({ loadMaps: true })) + .pipe(sourcemaps.write('.')) + .pipe(dest('./public/viewjs/components'))); + viewJStasks.push(target); +}); + +// The `clean` function is not exported so it can be considered a private task. +// It can still be used within the `series()` composition. +function clean(cb) +{ + // body omitted + cb(); +} + +// The `build` function is exported so it is public and can be run with the `gulp` command. +// It can also be used within the `series()` composition. +function build(cb) +{ + // body omitted + return parallel(js, css, vendor, resourceFileCopy); +} + +function publish(cb) +{ + minify = true; + postcss_plugins.push(cssnano()) + return build(); +} + +function js(cb) +{ + return rollup({ + input: './js/grocy.js', + output: { + format: 'umd', + name: 'grocy.js', + sourcemap: 'inline', + }, + plugins: [resolve(), commonjs()], + + }) + .pipe(source('grocy.js', "./js")) + .pipe(gulpif(minify, uglify())) + .pipe(buffer()) + .pipe(sourcemaps.init({ loadMaps: true })) + .pipe(sourcemaps.write('.')) + .pipe(dest('./public/js')); +} + +function viewjs(cb) +{ + return parallel(viewJStasks, done => { done(); cb(); })(); +} + + +function vendor(cb) +{ + return rollup({ + input: './js/vendor.js', + output: { + format: 'umd', + name: 'grocy.js', + sourcemap: 'inline', + }, + plugins: [resolve(), commonjs()], + }) + .pipe(source('vendor.js', "./js")) + .pipe(gulpif(minify, uglify())) + .pipe(buffer()) + .pipe(sourcemaps.init({ loadMaps: true })) + .pipe(sourcemaps.write('.')) + .pipe(dest('./public/js')); +} + +function css(cb) +{ + return src('./scss/grocy.scss') + .pipe(sourcemaps.init()) + .pipe(gulpsass({ includePaths: ['./node_modules'], quietDeps: true }).on('error', gulpsass.logError)) + .pipe(concatCss('grocy.css', { includePaths: ['./node_modules'], rebaseUrls: false })) + .pipe(postcss(postcss_plugins)) + .pipe(sourcemaps.write('.')) + .pipe(dest('./public/css')) +} + +function resourceFileCopy(cb) +{ + return parallel( + cb => src([ + './node_modules/@fortawesome/fontawesome-free/webfonts/*' + ]).pipe(dest('./public/webfonts')), + cb => src('./node_modules/summernote/dist/font/*').pipe(dest('./public/css/font')), + done => { done(); cb(); } + )(); +} + +function copyLocales(cb) +{ + return parallel( + cb => src('./node_modules/timeago/locales/*.js').pipe(dest('./public/js/locales/timeago')), + cb => src('./node_modules/summernote/dist/lang/*.js').pipe(dest('./public/js/locales/summernote')), + cb => src('./node_modules/bootstrap-select/dist/js/i18n/*').pipe(dest('./public/js/locales/bootstrap-select')), + cb => src('./node_modules/fullcalendar/dist/locale/*').pipe(dest('./public/js/locales/fullcalendar')), + cb => src('./node_modules/@fullcalendar/core/locales/*').pipe(dest('./public/js/locales/fullcalendar-core')), + done => { done(); cb(); } + )(); +} + +function live(cb) +{ + watch('./scss/**/*.scss', css); + watch(['./js/**/*.js', '!!./js/viewjs/**/*.js'], js); + watch('./js/vendor.js', vendor); + //watch('./js/viewjs/**/*.js', viewjs); + viewJStasks.forEach(elem => watch(elem, series([elem]))); +} + +export { build, js, vendor, viewjs, css, live, clean, resourceFileCopy, copyLocales } \ No newline at end of file diff --git a/js/configs/datatable.js b/js/configs/datatable.js new file mode 100644 index 00000000..fbe68753 --- /dev/null +++ b/js/configs/datatable.js @@ -0,0 +1,113 @@ +import { IsJsonString } from '../helpers/extensions'; + +function setDatatableDefaults(Grocy) +{ + // Default DataTables initialisation settings + var collapsedGroups = {}; + $.extend(true, $.fn.dataTable.defaults, { + 'paginate': false, + 'deferRender': true, + 'language': IsJsonString(__t('datatables_localization')) ? JSON.parse(__t('datatables_localization')) : {}, + 'scrollY': false, + 'scrollX': true, + 'colReorder': true, + 'stateSave': true, + 'stateSaveParams': function(settings, data) + { + data.search.search = ""; + + data.columns.forEach(column => + { + column.search.search = ""; + }); + }, + 'stateSaveCallback': function(settings, data) + { + var settingKey = 'datatables_state_' + settings.sTableId; + if ($.isEmptyObject(data)) + { + //state.clear was called and unfortunately the table is not refresh, so we are reloading the page + Grocy.FrontendHelpers.DeleteUserSetting(settingKey, true); + } else + { + var stateData = JSON.stringify(data); + Grocy.FrontendHelpers.SaveUserSetting(settingKey, stateData); + } + }, + 'stateLoadCallback': function(settings, data) + { + var settingKey = 'datatables_state_' + settings.sTableId; + + if (Grocy.UserSettings[settingKey] == undefined) + { + return null; + } + else + { + return JSON.parse(Grocy.UserSettings[settingKey]); + } + }, + 'preDrawCallback': function(settings) + { + // Currently it is not possible to save the state of rowGroup via saveState events + var api = new $.fn.dataTable.Api(settings); + if (typeof api.rowGroup === "function") + { + var settingKey = 'datatables_rowGroup_' + settings.sTableId; + if (Grocy.UserSettings[settingKey] !== undefined) + { + var rowGroup = JSON.parse(Grocy.UserSettings[settingKey]); + + // Check if there way changed. the draw event is called often therefore we have to check if it's really necessary + if (rowGroup.enable !== api.rowGroup().enabled() + || ("dataSrc" in rowGroup && rowGroup.dataSrc !== api.rowGroup().dataSrc())) + { + + api.rowGroup().enable(rowGroup.enable); + + if ("dataSrc" in rowGroup) + { + api.rowGroup().dataSrc(rowGroup.dataSrc); + + // Apply fixed order for group column + var fixedOrder = { + pre: [rowGroup.dataSrc, 'asc'] + }; + + api.order.fixed(fixedOrder); + } + } + } + } + }, + 'columnDefs': [ + { type: 'chinese-string', targets: '_all' } + ], + 'rowGroup': { + enable: false, + startRender: function(rows, group) + { + var collapsed = !!collapsedGroups[group]; + var toggleClass = collapsed ? "fa-caret-right" : "fa-caret-down"; + + rows.nodes().each(function(row) + { + row.style.display = collapsed ? "none" : ""; + }); + + return $("") + .append('' + group + ' ') + .attr("data-name", group) + .toggleClass("collapsed", collapsed); + } + } + }); + $(document).on("click", "tr.dtrg-group", function() + { + var name = $(this).data('name'); + collapsedGroups[name] = !collapsedGroups[name]; + $("table").DataTable().draw(); + }); +} + +export { setDatatableDefaults } \ No newline at end of file diff --git a/js/configs/globalstate.js b/js/configs/globalstate.js new file mode 100644 index 00000000..a3f45ef8 --- /dev/null +++ b/js/configs/globalstate.js @@ -0,0 +1,497 @@ +import { ResizeResponsiveEmbeds } from "../helpers/embeds"; +import { IsTouchInputDevice } from "../helpers/input"; +import { BoolVal } from "../helpers/extensions"; + + +// This function sets some global state and adds some global event listeners. +function setInitialGlobalState(Grocy) +{ + // __t isn't set in global context yet, make one locally. + var __t = function(key, ...placeholder) { return Grocy.translate(key, ...placeholder) }; + + if (!Grocy.ActiveNav.isEmpty()) + { + var menuItem = $('#sidebarResponsive').find("[data-nav-for-page='" + Grocy.ActiveNav + "']"); + menuItem.addClass('active-page'); + + if (menuItem.length) + { + var parentMenuSelector = menuItem.data("sub-menu-of"); + if (typeof parentMenuSelector !== "undefined") + { + $(parentMenuSelector).collapse("show"); + $(parentMenuSelector).prev(".nav-link-collapse").addClass("active-page"); + + $(parentMenuSelector).on("shown.bs.collapse", function(e) + { + if (!menuItem.isVisibleInViewport(75)) + { + menuItem[0].scrollIntoView(); + } + }) + } + else + { + if (!menuItem.isVisibleInViewport(75)) + { + menuItem[0].scrollIntoView(); + } + } + } + } + + var observer = new MutationObserver(function(mutations) + { + mutations.forEach(function(mutation) + { + if (mutation.attributeName === "class") + { + var attributeValue = $(mutation.target).prop(mutation.attributeName); + if (attributeValue.contains("sidenav-toggled")) + { + window.localStorage.setItem("sidebar_state", "collapsed"); + } + else + { + window.localStorage.setItem("sidebar_state", "expanded"); + } + } + }); + }); + observer.observe(document.body, { + attributes: true + }); + + if (window.localStorage.getItem("sidebar_state") === "collapsed") + { + $("#sidenavToggler").click(); + } + + window.toastr.options = { + toastClass: 'alert', + closeButton: true, + timeOut: 20000, + extendedTimeOut: 5000 + }; + + window.FontAwesomeConfig = { + searchPseudoElements: true + } + + $(window).on('resize', function() + { + ResizeResponsiveEmbeds($("body").hasClass("fullscreen-card")); + }); + $("iframe").on("load", function() + { + ResizeResponsiveEmbeds($("body").hasClass("fullscreen-card")); + }); + + // Don't show tooltips on touch input devices + if (IsTouchInputDevice()) + { + var css = document.createElement("style"); + css.innerHTML = ".tooltip { display: none; }"; + document.body.appendChild(css); + } + + $(document).on("keyup paste change", "input, textarea", function() + { + $(this).closest("form").addClass("is-dirty"); + }); + $(document).on("click", "select", function() + { + $(this).closest("form").addClass("is-dirty"); + }); + + // Auto saving user setting controls + $(document).on("change", ".user-setting-control", function() + { + var element = $(this); + var settingKey = element.attr("data-setting-key"); + + if (!element[0].checkValidity()) + { + return; + } + + var inputType = "unknown"; + if (typeof element.attr("type") !== typeof undefined && element.attr("type") !== false) + { + inputType = element.attr("type").toLowerCase(); + } + + if (inputType === "checkbox") + { + value = element.is(":checked"); + } + else + { + var value = element.val(); + } + + Grocy.FrontendHelpers.SaveUserSetting(settingKey, value); + }); + + // Show file name Bootstrap custom file input + $('input.custom-file-input').on('change', function() + { + $(this).next('.custom-file-label').html(GetFileNameFromPath($(this).val())); + }); + + // Translation of "Browse"-button of Bootstrap custom file input + if ($(".custom-file-label").length > 0) + { + $("