diff --git a/.gitignore b/.gitignore index ac4bd4ca8..5acc3f655 100755 --- a/.gitignore +++ b/.gitignore @@ -18,6 +18,5 @@ package-lock.json /src/assets/style/less/g3w-skins-custom/ /src/libs/sdk/config/dev/ /src/libs/sdk/g3w-ol3/config/config.js -/src/index.html /src/plugins/** !/src/plugins/README.md diff --git a/README.md b/README.md index 185180d70..cc4e2f878 100755 --- a/README.md +++ b/README.md @@ -108,8 +108,8 @@ If everything went fine, you can now visit you local development server URL to s # project_type = "qdjango"; # project_id = "1" -http://localhost:8000/en/map/countries/qdjango/1 # g3w-admin (production) -http://localhost:3000/?project=countries/qdjango/1 # g3w-client (development) +http://localhost:8000/en/map/countries/qdjango/1 # g3w-admin (production) +http://localhost:3000/en/map/countries/qdjango/1 # g3w-client (development) ``` ```sh @@ -118,10 +118,23 @@ http://localhost:3000/?project=countries/qdjango/1 # g3w-client (development) # project_type = "qdjango"; # project_id = "2" -http://localhost:8000/en/map/eleprofile/qdjango/2 # g3w-admin (production) -http://localhost:3000/?project=eleprofile/qdjango/2 # g3w-client (development) +http://localhost:8000/en/map/eleprofile/qdjango/2 # g3w-admin (production) +http://localhost:3000/en/map/eleprofile/qdjango/2 # g3w-client (development) ``` +### Plugins + +If you want develop custom plugins you need to place them in the [`src/plugins`](https://github.com/g3w-suite/g3w-client/blob/dev/src/plugins) folder, below you can see some examples: + +- [base-template](https://github.com/g3w-suite/g3w-client-plugin-base-template) +- [editing](https://github.com/g3w-suite/g3w-client-plugin-editing) +- [eleprofile](https://github.com/g3w-suite/g3w-client-plugin-elevation-profile) +- [openrouteservice](https://github.com/g3w-suite/g3w-client-plugin-openrouteservice) +- [qplotly](https://github.com/g3w-suite/g3w-client-plugin-qplotly) +- [qtimeseries](https://github.com/g3w-suite/g3w-client-plugin-qtimeseries) +- [queryresult-template](https://github.com/g3w-suite/g3w-client-plugin-queryresult-template) +- [sidebar-template](https://github.com/g3w-suite/g3w-client-plugin-sidebar-template) + --- ### FAQ @@ -217,8 +230,8 @@ All notable changes to this project are documented in the [releases](https://git --- **Compatibile with:** -[![g3w-admin version](https://img.shields.io/badge/g3w--admin-3.4-1EB300.svg?style=flat)](https://github.com/g3w-suite/g3w-admin/tree/v.3.4.x) -[![g3w-suite-docker version](https://img.shields.io/badge/g3w--suite--docker-3.4-1EB300.svg?style=flat)](https://github.com/g3w-suite/g3w-suite-docker/tree/v3.4.x) +[![g3w-admin version](https://img.shields.io/badge/g3w--admin-3.5-1EB300.svg?style=flat)](https://github.com/g3w-suite/g3w-admin/tree/v.3.5.x) +[![g3w-suite-docker version](https://img.shields.io/badge/g3w--suite--docker-3.5-1EB300.svg?style=flat)](https://github.com/g3w-suite/g3w-suite-docker/tree/v3.5.x) --- diff --git a/config.template.js b/config.template.js index 8c45eab2c..1bc1e9e2a 100755 --- a/config.template.js +++ b/config.template.js @@ -4,68 +4,59 @@ const G3W_HOST_SCHEMA = 'http'; const G3W_HOST = '127.0.0.1'; // local development server const G3W_ADMIN_PORT = '8000'; // G3W-ADMIN development server const G3W_CLIENT_PORT = '3000'; // G3W-CLIENT development server -const G3W_PROXY_ROUTES = [ // G3W-ADMIN routes to be proxied while developing - '/media', - '/api', - '/ows', - '/static', - '/en/', - '/it/', - '/upload/' -]; -const G3W_PLUGINS = { // override "initConfig->group->plugins" attribute for custom plugin development - // "your-plugin-folder-name": { - // baseurl: '../dist', - // gid: 'qdjango:1' // 1 = current project id - // } -}; +const G3W_PLUGINS = [ // override "initConfig->group->plugins" attribute for custom plugin development + // "your-plugin-folder-name-1", + // "your-plugin-folder-name-2", + // "your-plugin-folder-name-3", +]; const G3W_KEYS = { // google: '', // bing: '' }; -const G3W_ADMIN_PATH = '../g3w-admin/g3w-admin'; // path to G3W-ADMIN main code - let conf = { - assetsFolder: './src/assets', // path to G3W-CLIENT assets folder - pluginsFolder: './src/plugins', // path to G3W-CLIENT plugins folder - distFolder: './dist', // path to G3W-CLIENT dist folder - clientFolder: './dist/client', // path to G3W-CLIENT client folder - admin_plugins_folder: G3W_ADMIN_PATH, // path to G3W-ADMIN where are stored all plugin folders - admin_static_folder: `${G3W_ADMIN_PATH}/client/static`, // path to G3W-ADMIN client/static - admin_templates_folder: `${G3W_ADMIN_PATH}/client/templates`, // path to G3W-ADMIN client/templates + assetsFolder: './src/assets', // path to G3W-CLIENT assets folder + pluginsFolder: './src/plugins', // path to G3W-CLIENT plugins folder + admin_plugins_folder: '../g3w-admin/g3w-admin', // path to G3W-ADMIN plugins folder + admin_overrides_folder: '../g3w-suite-docker/config/g3w-suite/overrides', // path to G3W-SUITE overrides folder host: G3W_HOST, port: G3W_CLIENT_PORT, // proxy configuration for local G3W_ADMIN server (where G3W-ADMIN is running) proxy: { host: G3W_HOST, url: `${G3W_HOST_SCHEMA}://${G3W_HOST}:${G3W_ADMIN_PORT}/`, - routes: G3W_PROXY_ROUTES }, test: { path: '/test/config/groups/' }, - createProject: { - before(project) { /* code here */ }, - after(project) { /* code here */ }, - }, - setCurrentProject: { - before(project) { /* code here */ }, - after(project) { /* code here */ }, - }, - // override "initConfig->group->plugins" attribute for custom plugin development plugins: G3W_PLUGINS, - keys: G3W_KEYS + keys: G3W_KEYS, + devConfig: function() { + g3wsdk.core.ApplicationService.once('ready', () => { }); + g3wsdk.core.project.ProjectsRegistry.onbefore('createProject', (projectConfig) => {}); + g3wsdk.core.project.ProjectsRegistry.onafter('createProject', (projectConfig) => {}); + g3wsdk.core.project.ProjectsRegistry.onbefore('setCurrentProject', (project) => {}); + g3wsdk.core.project.ProjectsRegistry.onafter('setCurrentProject', (project) => {}); + g3wsdk.gui.GUI.once('ready', () => { console.log('ready'); }); + } }; // backward compatibilities (v3.x) if (version < '4') { - conf.assetsFolder = (version < '3.7' ? './assets' : conf.assetsFolder); - conf.proxy.urls = conf.proxy.routes; - conf.localServerPort = conf.port; - conf.g3w_admin_paths = { + conf.assetsFolder = (version < '3.7' ? './assets' : conf.assetsFolder); + conf.distFolder = './dist'; + conf.clientFolder = './dist/client'; + conf.admin_static_folder = `${conf.admin_plugins_folder}/client/static`; + conf.admin_templates_folder = `${conf.admin_plugins_folder}/client/templates`; + conf.createProject = { before() {}, after() {} }; + conf.setCurrentProject = { before() {}, after() {} }; + conf.plugins = conf.plugins.reduce((a, v) => ({ ...a, [v]: { gid: 'qdjango:1', baseurl: `.${conf.distFolder}` }}), {}); + conf.proxy.routes = ['/media', '/api', '/ows', '/static', '/en/', '/it/', '/upload/']; + conf.proxy.urls = conf.proxy.routes; + conf.localServerPort = conf.port; + conf.g3w_admin_paths = { dev: { g3w_admin_plugins_basepath: conf.admin_plugins_folder.replace(/\/?$/, '/'), g3w_admin_client_dest_static: conf.admin_static_folder, diff --git a/gulpfile.js b/gulpfile.js index e368ae18b..f4b30dd0e 100755 --- a/gulpfile.js +++ b/gulpfile.js @@ -3,15 +3,15 @@ const gulp = require('gulp'); const cleanCSS = require('gulp-clean-css'); const concat = require('gulp-concat'); const flatten = require('gulp-flatten'); -const htmlreplace = require('gulp-html-replace'); const gulpif = require('gulp-if'); const less = require('gulp-less'); +const merge = require('gulp-merge'); const prompt = require('gulp-prompt'); const rename = require('gulp-rename'); const replace = require('gulp-replace'); +const sourcemaps = require('gulp-sourcemaps'); const uglify = require('gulp-uglify'); const gutil = require('gulp-util'); -const useref = require('gulp-useref'); // used to parse index.dev.html // Gulp vinyl (virtual memory filesystem stuff) const buffer = require('vinyl-buffer'); @@ -26,7 +26,6 @@ const path = require('path'); const babelify = require('babelify'); const browserSync = require('browser-sync'); const browserify = require('browserify'); -const httpProxy = require('http-proxy'); const karma = require('karma'); const imgurify = require('imgurify'); const LessGlob = require('less-plugin-glob'); @@ -43,7 +42,8 @@ const g3w = require('./config'); /////////////////////////////////////////////////////// // TODO: make use of "process.env" instead of setting local variables -let production = false; +let production = false; +let outputFolder = g3w.admin_overrides_folder; // Retrieve project dependencies ("g3w-client") const dependencies = Object.keys(packageJSON.dependencies).filter(dep => dep !== 'vue'); @@ -51,48 +51,95 @@ const dependencies = Object.keys(packageJSON.dependencies).filter(dep => dep !== // production const to set environmental variable function setNODE_ENV() { process.env.NODE_ENV = production ? 'production' : 'development'; + outputFolder = production ? g3w.admin_plugins_folder + '/client' : g3w.admin_overrides_folder; + console.log('[G3W-CLIENT] environment: ' + process.env.NODE_ENV); + console.log('[G3W-CLIENT] output folder: ' + outputFolder + '\n'); } setNODE_ENV(); -gulp.task('clean:dist', () => del([`${g3w.distFolder}/**/*`], { force: true })); -gulp.task('clean:vendor', () => del([`${g3w.clientFolder}/js/vendor.node_modules.min.js`], {force: true})); -gulp.task('clean:app', () => del([`${g3w.clientFolder}/js/app.js`, `${g3w.clientFolder}/css/app.css`], { force: true })); -gulp.task('clean:admin', () => del([`${g3w.admin_static_folder}/client/js/*`, `${g3w.admin_static_folder}/client/css/*`, `${g3w.admin_templates_folder}/client/index.html`], { force: true })); +// gulp.task('clean:dist', () => del([`${g3w.distFolder}/**/*`], { force: true })); +gulp.task('clean:dist', () => del([`${outputFolder}/static/*`, `${outputFolder}/templates/*`], { force: true })); +gulp.task('clean:admin', () => del([`${g3w.admin_plugins_folder}/client/static/*`, `${g3w.admin_plugins_folder}/client/templates/*`], { force: true })); +gulp.task('clean:overrides', () => del([`${g3w.admin_overrides_folder}/static/*`, `${g3w.admin_overrides_folder}/templates/*`], { force: true })); -gulp.task('browserify:vendor', function() { - return browserify( - /* Uncomment the following in next ESM release (v4.x) */ - // { - // plugin: [ - // esmify - // ], - // transform: [ - // vueify, - // [ babelify, { ignore: [/\/node_modules\//], /* global: true, sourceMaps: true, babelrc: true */ } ] - // [ stringify, { appliesTo: { includeExtensions: ['.html', '.xml'] } } ], - // imgurify - // ]} - ) - .require(dependencies) - .bundle() - .pipe(source('vendor.node_modules.min.js')) - .pipe(buffer()) - .pipe(uglify()) - .pipe(gulp.dest(`${g3w.clientFolder}/js`)); -}); +gulp.task('html', () => gulp.src('./src/index.html').pipe(gulp.dest(outputFolder + '/templates/client'))); +gulp.task('browser:reload', () => browserSync ? browserSync.reload() : null); /** - * Concatenate browserify vendor with vendor file inside assets specify in index.html.js + * Concatenate and browserify vendor javascript files */ -gulp.task('concatenate:vendor', function(){ - return gulp.src(`${g3w.clientFolder}/js/vendor.*.js`) +gulp.task('concatenate:vendor_js', function() { + return merge( + gulp.src([ + g3w.assetsFolder + "/vendors/jquery/jquery-2.2.1.min.js", + g3w.assetsFolder + "/vendors/jquery-ui/jquery-ui.min.js", + g3w.assetsFolder + "/vendors/bootstrap/js/bootstrap.min.js", + g3w.assetsFolder + "/vendors/bootbox/bootbox.min.js", + g3w.assetsFolder + "/vendors/lodash/lodash.min.js", + g3w.assetsFolder + "/vendors/eventemitter/EventEmitter.min.js", + g3w.assetsFolder + "/vendors/history/jquery.history.js", + g3w.assetsFolder + "/vendors/signals/signals.min.js", + g3w.assetsFolder + "/vendors/crossroads/crossroads.min.js", + g3w.assetsFolder + "/vendors/moment/moment.js", + g3w.assetsFolder + "/vendors/moment/moment-with-locales.js", + g3w.assetsFolder + "/vendors/bootstrap-datetimepicker/js/bootstrap-datetimepicker.min.js", + g3w.assetsFolder + "/vendors/icheck/icheck.min.js", + g3w.assetsFolder + "/vendors/bootstrap-treeview/js/bootstrap-treeview.js", + g3w.assetsFolder + "/vendors/slimScroll/jquery.slimscroll.min.js", + g3w.assetsFolder + "/vendors/fastclick/fastclick.js", + g3w.assetsFolder + "/vendors/vue/vue.min.js", + g3w.assetsFolder + "/vendors/vue/cookie/vue-cookie.js", + g3w.assetsFolder + "/vendors/vue-color/vue-color.js", + g3w.assetsFolder + "/vendors/jquery-file-upload/jquery.fileupload.js", + g3w.assetsFolder + "/vendors/jquery-fileDownload/jquery.fileDownload.js", + g3w.assetsFolder + "/vendors/bootstrap-filestyle/bootstrap-filestyle.min.js", + g3w.assetsFolder + "/vendors/ismobile/ismobile.min.js", + g3w.assetsFolder + "/vendors/jquery-i18next/jquery-i18next.min.js", + g3w.assetsFolder + "/vendors/i18next/i18next.min.js", + g3w.assetsFolder + "/vendors/i18next/i18nextXHRBackend.min.js", + g3w.assetsFolder + "/vendors/script/script.min.js", + g3w.assetsFolder + "/vendors/x2js/xml2json.g3w.min.js", + g3w.assetsFolder + "/vendors/proj4js/proj4.js", + g3w.assetsFolder + "/vendors/ol/js/ol.js", + g3w.assetsFolder + "/vendors/ol-rotate-feature/bundle.min.js", + g3w.assetsFolder + "/vendors/jsts/jsts.min.js", + g3w.assetsFolder + "/vendors/datatables/datatables.min.js", + g3w.assetsFolder + "/vendors/shp2geojson/shp.min.js", + g3w.assetsFolder + "/vendors/jszip/jszip.min.js", + g3w.assetsFolder + "/vendors/filesaver/FileSaver.min.js", + g3w.assetsFolder + "/vendors/select2/js/select2.full.min.js", + g3w.assetsFolder + "/vendors/select2/js/i18n/it.js", + g3w.assetsFolder + "/vendors/d3/js/d3.min.js", + g3w.assetsFolder + "/vendors/c3/js/c3.min.js", + g3w.assetsFolder + "/vendors/wps/js/wps-js-all.min.js", + g3w.assetsFolder + "/vendors/quill/js/quill.min.js" + ]), + browserify( + /* Uncomment the following in next ESM release (v4.x) */ + // { + // plugin: [ + // esmify + // ], + // transform: [ + // vueify, + // [ babelify, { ignore: [/\/node_modules\//], /* global: true, sourceMaps: true, babelrc: true */ } ] + // [ stringify, { appliesTo: { includeExtensions: ['.html', '.xml'] } } ], + // imgurify + // ]} + ) + .require(dependencies) + .bundle() + .pipe(source('vendor.node_modules.min.js')) + .pipe(buffer()) + .pipe(uglify()) + ) .pipe(concat('vendor.min.js')) - .pipe(gulp.dest(`${g3w.clientFolder}/js/`)); + .pipe(gulp.dest(outputFolder + '/static/client/js/')); }); /** - * Trasform modularized code in a browser compatible way + * Compile client application (src/app/main.js --> app.min.js) */ gulp.task('browserify:app', function() { let rebundle; @@ -106,12 +153,12 @@ gulp.task('browserify:app', function() { // plugin: [ // esmify // ], - // transform: [ - // vueify, - // [ babelify, { babelrc: true } ] - // [ stringify, { appliesTo: { includeExtensions: ['.html', '.xml'] } } ], - // imgurify - // ] + transform: [ + vueify, + [ babelify, { babelrc: true } ], + [ stringify, { appliesTo: { includeExtensions: ['.html', '.xml'] } } ], + imgurify + ] }); if (production) { bundler.ignore('./src/app/dev/index.js'); // ignore dev index file @@ -126,285 +173,259 @@ gulp.task('browserify:app', function() { bundler = watchify(bundler); } - // trasformation - bundler - .transform(vueify) - .transform(babelify, { babelrc: true }) - .transform(stringify, { appliesTo: { includeExtensions: ['.html', '.xml'] } }) - .transform(imgurify); - const bundle = () => bundler.bundle() .on('error', err => { console.log('ERROR: running gulp task "browserify:app"'); console.log(err); this.emit('end'); - del([ - `${g3w.clientFolder}/js/app.js`, - `${g3w.clientFolder}/style/app.css` - ]).then(() => process.exit()); + process.exit() + // del([ + // `${outputFolder}/static/js/app.js`, + // `${outputFolder}/static/css/app.css` + // ]).then(() => process.exit()); }) .pipe(source('build.js')) .pipe(buffer()) + .pipe(gulpif(production, sourcemaps.init())) .pipe(gulpif(production, uglify({ compress: { drop_console: true } }).on('error', gutil.log))) - .pipe(rename('app.js')) - .pipe(gulp.dest(`${g3w.clientFolder}/js/`)); + .pipe(rename('app.min.js')) + .pipe(gulpif(production, sourcemaps.write('.'))) + .pipe(gulp.dest(outputFolder + '/static/client/js/')); if (production) { rebundle = () => bundle(); } else { rebundle = () => bundle().pipe(browserSync.reload({ stream: true })); - bundler.on('update', rebundle); + bundler.on('update', rebundle); } return rebundle(); }); -gulp.task('fonts', function () { +/** + * Deploy client and vendor images + */ +gulp.task('images', function () { return gulp.src([ - `${g3w.assetsFolder}/fonts/**/*.{eot,ttf,woff,woff2}`, - `${g3w.pluginsFolder}/**/*.{eot,ttf,woff,woff2}`, - `!${g3w.pluginsFolder}/**/node_modules/**`, + `${g3w.assetsFolder}/images/**/*.{png,jpg,gif,svg}`, + `${g3w.pluginsFolder}/**/*.{png,jpg,gif,svg}`, + '!./src/**/node_modules/**/' ]) .pipe(flatten()) - .pipe(gulp.dest(`${g3w.clientFolder}/fonts/`)) + .pipe(gulp.dest(outputFolder + '/static/client/images/')) }); -gulp.task('images', function () { - return gulp.src([ - `${g3w.assetsFolder}/images/**/*.{png,jpg,gif,svg}`, - `${g3w.pluginsFolder}/**/*.{png,jpg,gif,svg}`, - `!${g3w.pluginsFolder}/**/node_modules/**` - ]) +/** + * Deploy datatables images (src/assets/vendors/datatables) + */ + gulp.task('datatable-images', function () { + return gulp.src(`${g3w.assetsFolder}/vendors/datatables/DataTables-1.10.16/images/*`) .pipe(flatten()) - .pipe(gulp.dest(g3w.clientFolder + '/images/')) + .pipe(gulp.dest(outputFolder + '/static/client/css/DataTables-1.10.16/images/')); }); /** - * Compile less file in css + * Deploy client and vendor fonts */ - gulp.task('less', ['fonts'], function() { + gulp.task('fonts', function () { return gulp.src([ - `${g3w.assetsFolder}/style/less/app.less`, - `${g3w.pluginsFolder}/*/style/less/plugin.less` + `${g3w.assetsFolder}/fonts/**/*.{eot,ttf,woff,woff2}`, + `${g3w.assetsFolder}/vendors/bootstrap/fonts/**/*.{eot,ttf,woff,woff2}`, + `${g3w.assetsFolder}/vendors/font-awesome-5.15.4/webfonts/**/*.{eot,ttf,woff,woff2}`, + `${g3w.pluginsFolder}/**/*.{eot,ttf,woff,woff2}`, + '!./src/**/node_modules/**/' ]) - .pipe(concat('app.less')) - .pipe(less({ - // add paths where to search in @import - paths: [ - `${g3w.assetsFolder}/style/less`, - `${g3w.pluginsFolder}/*/style/less` - ], - // plugin to manage globs import es: @import path/*** - plugins: [LessGlob] - })) - .pipe(gulp.dest(g3w.clientFolder + '/css/')) -}); - -gulp.task('datatable-images', function () { - return gulp.src(`${g3w.assetsFolder}/vendors/datatables/DataTables-1.10.16/images/*`) .pipe(flatten()) - .pipe(gulp.dest(g3w.clientFolder + '/css/DataTables-1.10.16/images/')); + .pipe(gulp.dest(outputFolder + '/static/client/fonts/')) }); -gulp.task('assets', ['fonts', 'images', 'less','datatable-images']); - /** - * Create external assets (css and javascript libraries) referenced within main html + * Compile client styles (src/assets/style/less/app.less --> app.min.css) */ - gulp.task('build_external_assets', function() { - const replaceRelativeAssetsFolder = path.relative(path.resolve('./src'), path.resolve(g3w.assetsFolder)) + '/' ; - const replaceRelativePluginFolder = function() { - const pluginName = path.dirname(this.file.relative); - return path.relative(path.resolve('./src'), path.resolve(`${g3w.pluginsFolder}/${pluginName}`)) + '/' ; - }; - return gulp.src('./src/index.dev.html') - // replace css and js sources - .pipe(htmlreplace({ - 'app_vendor_css': - gulp.src(`${g3w.assetsFolder}/vendors/index.css.html`) - .pipe(replace('./', replaceRelativeAssetsFolder)), - 'app_vendor_js': - gulp.src(`${g3w.assetsFolder}/vendors/index.js.html`) - .pipe(replace('./', replaceRelativeAssetsFolder)), - 'plugins_css': - gulp.src(`${g3w.pluginsFolder}/*/index.css.html`) - .pipe(replace('./', replaceRelativePluginFolder)), - 'plugins_js': - gulp.src(`${g3w.pluginsFolder}/*/index.js.html`) - .pipe(replace('./', replaceRelativePluginFolder)) - }) - ) - .pipe(rename('index.html')) - .pipe(gulp.dest('./src')); +gulp.task('less', ['fonts'], function() { + return gulp.src(`${g3w.assetsFolder}/style/less/app.less`) + .pipe(less({ + paths: [`${g3w.assetsFolder}/style/less`], // add paths where to search in @import + plugins: [LessGlob] // plugin to manage globs import es: @import path/*** + })) + //.pipe(gulpif(production, cleanCSS({ keepSpecialComments: 0 }), replace(/\w+fonts/g, 'fonts'))) + .pipe(replace(/\w+fonts/g, 'fonts')) // eg. "../webfonts/fa-regular-400.woff2" --> ""../fonts/fa-regular-400.woff2" + .pipe(cleanCSS({ keepSpecialComments: 0 })) + .pipe(rename('app.min.css')) + .pipe(gulp.dest(outputFolder + '/static/client/css/')) }); /** - * Create a index.html in src/ and add all external libraries and css to it + * Compile less files in css (process.env.CUSTOM_LESS_FOLDER) */ -gulp.task('html:dev', ['build_external_assets', 'assets'], function() { - return gulp.src('./src/index.html') - .pipe(useref()) - .pipe(gulpif(['css/app.min.css'], cleanCSS({ keepSpecialComments: 0 }), replace(/\w+fonts/g, 'fonts'))) - .pipe(gulp.dest(g3w.clientFolder)); +gulp.task('custom-less', function () { + const customLessFolder = path.join(g3w.assetsFolder, 'style', 'less', 'g3w-skins-custom', process.env.CUSTOM_LESS_FOLDER); + return gulp.src(path.join(customLessFolder, 'main.less')) + .pipe(concat('custom.less')) + .pipe(less({ + plugins: [LessGlob] //plugin to manage globs import es: @import path/*** + })) + .pipe(gulp.dest(`${customLessFolder}/css/`)) }); /** - * Build django g3w-admin template with the referenced of all css and js minified and added hash create by md5hash task + * Concatenate vendor css files */ -gulp.task('html:prod', function() { - return gulp.src('./src/index.prod.html') - .pipe(rename({ basename: 'index', extname: '.html' })) - .pipe(gulp.dest(g3w.clientFolder)); +gulp.task('concatenate:vendor_css', function() { + return gulp.src([ + g3w.assetsFolder + "/vendors/bootstrap/css/bootstrap.min.css", + g3w.assetsFolder + "/vendors/bootstrap-treeview/css/bootstrap-treeview.min.css", + g3w.assetsFolder + "/vendors/icheck/skins/all.css", + g3w.assetsFolder + "/vendors/magic-check/magic-check.min.css", + g3w.assetsFolder + "/vendors/bootstrap-datetimepicker/css/bootstrap-datetimepicker.min.css", + g3w.assetsFolder + "/vendors/hint/hint.min.css", + g3w.assetsFolder + "/vendors/ol/css/ol.css", + g3w.assetsFolder + "/vendors/select2/css/select2.min.css", + g3w.assetsFolder + "/vendors/c3/css/c3.min.css", + g3w.assetsFolder + "/vendors/datatables/DataTables-1.10.16/css/jquery.dataTables.min.css", + g3w.assetsFolder + "/vendors/font-awesome-5.15.4/css/all.min.css", + g3w.assetsFolder + "/vendors/quill/css/quill.snow.min.css" + ]) + .pipe(concat('vendor.min.css')) + .pipe(replace(/\w+fonts/g, 'fonts')) // eg. "../webfonts/fa-regular-400.woff2" --> ""../fonts/fa-regular-400.woff2" + .pipe(gulp.dest(outputFolder + '/static/client/css/')); }); +/** + * Proxy development server for local G3W-ADMIN instance + */ gulp.task('browser-sync', function() { - const proxy = httpProxy - .createProxyServer({ target: g3w.proxy.url }) - .on('error', e => gutil.log(e)); - browserSync.init({ - server: { - baseDir: ['src', '.'], - middleware: [ - (req, res, next) => { - let doproxy = false; - let rootUrl; - if (req.url.indexOf('plugin.js') > -1) rootUrl = req.url; - else rootUrl = req.url.split('?')[0]; - for (let i in g3w.proxy.routes) { - if (rootUrl.indexOf(g3w.proxy.routes[i]) > -1) { - doproxy = true; - break; - } - } - doproxy ? proxy.web(req, res) : next(); - } - ] - }, port: g3w.port, open: false, startPath: '/', + proxy: { + target: g3w.proxy.url + }, socket: { domain: `${g3w.host}:${g3w.port}` } }); -}); - -gulp.task('browser:reload', function() { - if (browserSync) { - browserSync.reload(); - } -}); - -/** - * Live reload application on code changes - */ -gulp.task('watch', function(done) { - /* Uncomment the following in next Gulp Release (v4.x) */ - // gulp.watch([g3w.assetsFolder + '/style/**/*.less', g3w.pluginsFolder + '/**/*.less'], gulp.series('less', 'browser:reload')); - // gulp.watch([g3w.assetsFolder + '/style/skins/*.less'], gulp.series('browser:reload')); - // gulp.watch('./src/**/*.{png,jpg}', gulp.series('images', 'browser:reload')); - // gulp.watch(g3w.pluginsFolder + '/**/plugin.js', gulp.series('plugins', 'browser:reload')); - // gulp.watch(g3w.pluginsFolder + '/**/style/less/plugin.less', gulp.series('less', 'browser:reload')); - // gulp.watch([g3w.pluginsFolder + '/*/index.*.html'], gulp.series('build_external_assets', 'browser:reload')); - // gulp.watch(g3w.assetsFolder + '/vendors/index.*.html', gulp.series('build_external_assets', 'browser:reload')); - // gulp.watch(['./src/index.html', './src/**/*.html'], gulp.series('browser:reload')); - gulp.watch([g3w.assetsFolder + '/style/**/*.less', g3w.pluginsFolder + '/**/*.less'], () => runSequence('less','browser:reload')); - gulp.watch([g3w.assetsFolder + '/style/skins/*.less'], () => runSequence('less:skins','browser:reload')); - gulp.watch('./src/**/*.{png,jpg}', () => runSequence('images','browser:reload')); - gulp.watch(g3w.pluginsFolder + '/**/plugin.js', () => runSequence('plugins','browser:reload')); - gulp.watch(g3w.pluginsFolder + '/**/style/less/plugin.less', () => runSequence('less','browser:reload')); - gulp.watch(g3w.pluginsFolder + '/*/index.*.html', () => runSequence('build_external_assets','browser:reload')); - gulp.watch(g3w.assetsFolder + './assets/vendors/index.*.html', () => runSequence('build_external_assets','browser:reload')); - gulp.watch(['./src/index.html','./src/**/*.html'], () => browserSync.reload()); - done(); -}); - -/** - * Run the following tasks sequentially: - * - * 1. clean dist folder - * 2. set production variable to true - * 3. browserify all files (require) - * 4. read index.html after compiled less, fonts etc .. and read build blocks - * concatenate and insert .min.css/js version - * 5. write django g3w-admin template subtitude suffix .min with current version - * 6. Remove app.js and app.css from g3w-admin client folder - */ -gulp.task('dist', done => runSequence('clean:dist', 'production', 'browserify:app', 'browserify:vendor', 'html:dev', 'concatenate:vendor', 'clean:vendor', 'html:prod', 'clean:app', done)); -/** - * Copy all plugins to g3w-admin's plugin folder - */ -gulp.task('plugins', function() { - return gulp.src(`${g3w.pluginsFolder}/*/plugin.js`) - .pipe(rename((path) => { path.dirname = g3w.distFolder + '/' + path.dirname + '/js/'; })) - .pipe(gulp.dest('.')); + /* Uncomment the following in next Gulp Release (v4.x) */ + // + // gulp.watch([g3w.assetsFolder + '/style/**/*.less'], gulp.series('less', 'browser:reload')); + // gulp.watch('./src/**/*.{png,jpg}', gulp.series('images', 'browser:reload')); + // gulp.watch(['./src/index.html', './src/**/*.html'], gulp.series('browser:reload')); + // + + gulp.watch([g3w.assetsFolder + '/style/**/*.less'], () => runSequence('less','browser:reload')); + gulp.watch('./src/**/*.{png,jpg}', () => runSequence('images','browser:reload')); + gulp.watch(['./src/index.html'], () => runSequence('html', 'browser:reload')); + gulp.watch(g3w.pluginsFolder + '/*/plugin.js', (file) => { + const plugins = process.env.G3W_PLUGINS; + process.env.G3W_PLUGINS = path.basename(path.dirname(file.path)); + runSequence('deploy-plugins', 'browser:reload', () => process.env.G3W_PLUGINS = plugins) + }); }); /** - * Lets developer choose which plugins to include within generated bundle + * Ask the developer which plugins wants to deploy */ gulp.task('select-plugins', function() { return gulp .src('./package.json') + .pipe( + prompt.prompt({ + type: 'list', + name: 'env', + message: 'Environment', + choices: ['development', 'production'], + }, (response) => { + production = response.env == 'production'; + setNODE_ENV(); + } + ) + ) .pipe( prompt.prompt({ type: 'checkbox', name: 'plugins', message: 'Plugins', // exclude from plugin list "client" and all "template_" plugins - choices: fs.readdirSync(g3w.distFolder).filter(file => file !== 'client' && file.indexOf('template_') === -1 && fs.statSync(`${g3w.distFolder}/${file}`).isDirectory()) + choices: fs.readdirSync(g3w.pluginsFolder).filter(file => { + try { + return file !== 'client' + && file.indexOf('template_') === -1 + && fs.statSync(`${g3w.pluginsFolder}/${file}`).isDirectory() + && fs.statSync(`${g3w.pluginsFolder}/${file}/plugin.js`).isFile(); + } catch (e) { + console.warn(`[WARN] file not found: ${g3w.pluginsFolder}/${file}/plugin.js`); + return false; + } + }) }, response => process.env.G3W_PLUGINS = response.plugins ) ); }); -gulp.task('deploy-plugins', function(done) { - const pluginNames = process.env.G3W_PLUGINS.split(','); - if (pluginNames.length === 1 && pluginNames[0] === '') { - console.log('No plugin selected'); - done(); - } else { - return gulp.src(pluginNames.map(pluginName => `${g3w.distFolder}/${pluginName}*/js/plugin.js`)) - .pipe(rename(path => { - const pluginname = path.dirname.replace('/js', ''); - path.dirname = `${g3w.admin_plugins_folder}/${pluginname}/static/${pluginname}/js/`; - })) - .pipe(gulp.dest('.')); - } +/** + * Deploy local developed plugins (src/plugins) + */ +gulp.task('deploy-plugins', function() { + const pluginNames = process.env.G3W_PLUGINS.split(','); + const nodePath = path; + const outputFolder = production ? g3w.admin_plugins_folder : g3w.admin_overrides_folder + '/static'; + return gulp.src(pluginNames.map(pluginName => `${g3w.pluginsFolder}/${pluginName}/plugin.js`)) + .pipe(rename((path, file) => { + const pluginName = nodePath.basename(file.base); + const staticFolder = production ? `${pluginName}/static/${pluginName}/js/` : `${pluginName}/js/`; + path.dirname = `${outputFolder}/${staticFolder}`; + console.log(`[G3W-CLIENT] file updated: ${path.dirname}${path.basename}${path.extname}`); + })) + .pipe(gulp.dest('.')); }); /** - * Task plugins + * Deploy local developed plugins (src/plugins) */ -gulp.task('g3w-admin:plugins', done => runSequence('plugins', 'select-plugins', 'deploy-plugins', done)); +gulp.task('build:plugins', (done) => runSequence('select-plugins', 'deploy-plugins', done)); -gulp.task('g3w-admin:static', function() { - return gulp.src([ - `${g3w.clientFolder}/**/*.*`, - `!${g3w.clientFolder}/index.html`, - `!${g3w.clientFolder}/js/app.js`, - `!${g3w.clientFolder}/css/app.css` - ]) - .pipe(gulp.dest(`${g3w.admin_static_folder}/client/`)); -}); +/** + * Compile and deploy local developed client file assets (static and templates) + */ +gulp.task('build:client', ['browserify:app', 'concatenate:vendor_js', 'concatenate:vendor_css', 'fonts', 'images', 'less', 'datatable-images', 'html']); /** - * Copy local index.html to admin-client folder template + * [PROD] Compile and deploy client application + * + * production = true, + * outputFolder = g3w.admin_plugins_folder + '/client' */ -gulp.task('g3w-admin:templates', function() { - return gulp.src(`${g3w.clientFolder}/index.html`) - .pipe(gulp.dest(`${g3w.admin_templates_folder}/client/`)); -}); +gulp.task('build', done => runSequence( + 'production', + 'clean:admin', + 'clean:overrides', + 'build:client', + done + ) +); /** - * Create g3w-admin files. It start from compile sdk source folder, app source folder and all plugins + * [DEV] Compile and deploy client application + * + * production = false, + * outputFolder = g3w.admin_overrides_folder */ -gulp.task('g3w-admin', done => runSequence('dist', 'clean:admin', 'g3w-admin:static', 'g3w-admin:templates', 'g3w-admin:plugins', done)); +gulp.task('dev', done => runSequence( + 'clean:admin', + 'clean:overrides', + 'build:client', + 'browser-sync', + done + ) +); /** - * Run test once and exit + * [TEST] Run test once and exit + * + * production = false, + * outputFolder = g3w.admin_overrides_folder */ gulp.task('test', function() { return new Promise(async done => { @@ -422,11 +443,6 @@ gulp.task('test', function() { }); }); -/** - * Deafult development task (BrowserSync server) - */ -gulp.task('dev', done => runSequence('build_external_assets', 'clean:dist', 'browserify:app', 'assets', 'plugins', 'watch', 'browser-sync', done)); - /** * Expose version of "package.json" without including whole file in published bundle, * this happens because each ESM `import` is actually transformed into a CJS `require()` @@ -445,48 +461,22 @@ gulp.task('version', function () { }); }); -// Backward compatibilities (v3.x) -gulp.task('clean', ['clean:dist']); -gulp.task('clean_vendor_node_modules_min', ['clean:vendor']); -gulp.task('cleanup', ['clean:app']); -gulp.task('g3w-admin-client:clear', ['clean:admin']); -gulp.task('sethasvalues', []); -gulp.task('concatenate_node_modules_vendor_min', ['concatenate:vendor']); -gulp.task('browserify', ['browserify:app']); -gulp.task('add_external_resources_to_main_html', ['build_external_assets']); -gulp.task('html', ['html:dev']); -gulp.task('html:compiletemplate', ['html:prod']); -gulp.task('g3w-admin-plugins-select', ['g3w-admin:plugins']); -gulp.task('g3w-admin-client:static', ['g3w-admin:static']); -gulp.task('g3w-admin-client:template', ['g3w-admin:templates']); -gulp.task('serve', ['dev']); -gulp.task('g3w-admin-client_test', [/*'g3w-admin-client:static','g3w-admin-client:template', 'g3w-admin-client:check_client_version'*/]); -gulp.task('g3w-admin-client', ['g3w-admin']); -gulp.task('default', ['dev']); - -////////////////////////////////////////////////////////////////////////// -/** - * TODO: check which of the following tasks is actually required - */ -////////////////////////////////////////////////////////////////////////// - -// compile less file in css -gulp.task('custom-less', function () { - const customLessFolder = path.join(g3w.assetsFolder, 'style', 'less', 'g3w-skins-custom', process.env.CUSTOM_LESS_FOLDER); - return gulp.src(path.join(customLessFolder, 'main.less')) - .pipe(concat('custom.less')) - .pipe(less({ - plugins: [LessGlob] //plugin to manage globs import es: @import path/*** - })) - .pipe(gulp.dest(`${customLessFolder}/css/`)) -}); - /** * Set production to true */ -gulp.task('production', function(){ + gulp.task('production', function(){ production = true; setNODE_ENV(); }); -////////////////////////////////////////////////////////////////////////// \ No newline at end of file +// Backward compatibilities (v3.x) +gulp.task('g3w-admin', ['build']); +gulp.task('g3w-admin-plugins-select', ['build:plugins']); +gulp.task('g3w-admin-client:static', ['build:static']); +gulp.task('g3w-admin-client:template', ['build:templates']); +gulp.task('g3w-admin-client', ['g3w-admin']); +gulp.task('g3w-admin:plugins', ['build:plugins']); +gulp.task('g3w-admin:static', ['build:static']); +gulp.task('g3w-admin:templates', ['build:templates']); +gulp.task('serve', ['dev']); +gulp.task('default', ['dev']); \ No newline at end of file diff --git a/package.json b/package.json index c6bc521d4..34b29a956 100755 --- a/package.json +++ b/package.json @@ -5,8 +5,8 @@ "main": "index.js", "scripts": { "preinstall": "npx npm-force-resolutions", - "build": "gulp version && gulp g3w-admin --max-old-space-size=2048", - "build:plugins": "gulp version && gulp g3w-admin:plugins", + "build": "gulp version && gulp build --max-old-space-size=2048", + "build:plugins": "gulp version && gulp build:plugins", "dev": "gulp dev --max-old-space-size=2048", "docker": "docker compose --env-file ../g3w-suite-docker/.env --file ../g3w-suite-docker/docker-compose-dev.yml --project-name g3w-suite-docker --project-directory ../g3w-suite-docker", "docker:pull": "npm run docker pull", diff --git a/src/app/dev/index.js b/src/app/dev/index.js index b629bd955..c263f1400 100644 --- a/src/app/dev/index.js +++ b/src/app/dev/index.js @@ -1,47 +1,50 @@ const { createProject = {}, - setCurrentProject = {}, - plugins = {}, - keys = {} + setCurrentProject = {}, + plugins = {}, + keys = {}, + devConfig = function() {} } = require('../../../config'); -const ProjectsRegistry = require('core/project/projectsregistry'); -const ApplicationService = require('core/applicationservice'); -// const GUI = require('gui/gui'); - -// ApplicationService.once('ready', () => {}); +const ApplicationService = g3wsdk.core.ApplicationService; +const ProjectsRegistry = g3wsdk.core.project.ProjectsRegistry; +const GUI = g3wsdk.gui.GUI; ApplicationService.once('initconfig', () => { // sets "initConfig->group->plugins" - Object.keys(plugins).forEach((plugin) => { - // TODO: make use of a recursive merge utility function ? - window.initConfig.group.plugins[plugin] = window.initConfig.group.plugins[plugin] ? { - ...window.initConfig.group.plugins[plugin], - ...plugins[plugin], - } : plugins[plugin]; + Object.keys(plugins || {}).forEach((plugin) => { + plugins[plugin].gid = plugins[plugin].gid || initConfig.group.initproject; + plugins[plugin].baseUrl = plugins[plugin].baseUrl || initConfig.staticurl; + initConfig.group.plugins[plugin] = { ...(initConfig.group.plugins[plugin] || {}), ...plugins[plugin] }; }); // sets "initConfig->group->vendorkeys" - if (Object.keys(keys).length > 0) { - window.initConfig.group.vendorkeys = window.initConfig.group.vendorkeys || {}; - Object.keys(keys).forEach((key) => { window.initConfig.group.vendorkeys[key] = keys[key]; }); - ApplicationService.setVendorKeys(keys); - } + Object.keys(keys || {}).forEach((key) => { + initConfig.group.vendorkeys = initConfig.group.vendorkeys || {}; + initConfig.group.vendorkeys[key] = keys[key]; + }); }); +/** @deprecated */ if (createProject.before) { ProjectsRegistry.onbefore('createProject', (projectConfig) => createProject.before(projectConfig)); } +/** @deprecated */ if (createProject.after) { ProjectsRegistry.onafter('createProject', (projectConfig) => createProject.after(projectConfig)); } +/** @deprecated */ if (setCurrentProject.before) { ProjectsRegistry.onbefore('setCurrentProject', (project) => setCurrentProject.before(project)); } +/** @deprecated */ if (setCurrentProject.after) { ProjectsRegistry.onafter('setCurrentProject', (project) => setCurrentProject.after(project)); } -// GUI.once('ready', () => {}); \ No newline at end of file +/** @TODO find a better way to visually distinguish production and development environments (ie. even for logged in users) */ +//GUI.once('ready', () => { document.body.classList.replace('skin-yellow', 'skin-blue'); }); + +devConfig.call(); \ No newline at end of file diff --git a/src/app/main.js b/src/app/main.js index d917597ee..30741710d 100755 --- a/src/app/main.js +++ b/src/app/main.js @@ -48,6 +48,8 @@ const GUI = require('gui/gui'); */ window.g3wsdk = require('api'); +require('app/dev'); + /** * EXPERIMENTAL: not yet implemented * diff --git a/src/assets/vendors/index.css.html b/src/assets/vendors/index.css.html index ccbe78fcf..e69de29bb 100644 --- a/src/assets/vendors/index.css.html +++ b/src/assets/vendors/index.css.html @@ -1,14 +0,0 @@ - - - - - - - - - - - - - - diff --git a/src/assets/vendors/index.js.html b/src/assets/vendors/index.js.html index def02f8bb..e69de29bb 100644 --- a/src/assets/vendors/index.js.html +++ b/src/assets/vendors/index.js.html @@ -1,43 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/index.dev.html b/src/index.dev.html deleted file mode 100644 index 0711cf3da..000000000 --- a/src/index.dev.html +++ /dev/null @@ -1,42 +0,0 @@ - - - - - - G3W Client - - - - - - - - - - - - - - - - - -
- -
- - - - - - - - - - - - - - - - diff --git a/src/index.prod.html b/src/index.html similarity index 100% rename from src/index.prod.html rename to src/index.html diff --git a/src/plugins/README.md b/src/plugins/README.md index c97284f9c..160b86a45 100644 --- a/src/plugins/README.md +++ b/src/plugins/README.md @@ -331,4 +331,4 @@ Within the **gulpfile.js** file is defined a `plugins` task which is used to sel ```sh npm run plugins -``` \ No newline at end of file +``` diff --git a/src/services/application.js b/src/services/application.js index 5951ead10..5da24bbab 100644 --- a/src/services/application.js +++ b/src/services/application.js @@ -373,50 +373,46 @@ const ApplicationService = function() { this.obtainInitConfig = async function({initConfigUrl, url, host}={}) { if (!this._initConfigUrl) this._initConfigUrl = initConfigUrl; else this.clearInitConfig(); - // if exist a global initiConfig (in production) - if (window.initConfig) { - this._initConfig = window.initConfig; - this.setInitVendorKeys(initConfig); - this.emit('initconfig', initConfig); - return window.initConfig; - // case development need to ask to api - } else { - // LOAD DEVELOPMENT CONFIGURATION - require('app/dev/index'); - let projectPath; - let queryTuples; - const locationsearch = url ? url.split('?')[1] : location.search ? location.search.substring(1) : null; - if (locationsearch) { - queryTuples = locationsearch.split('&'); - queryTuples.forEach(queryTuple => { - //check if exist project in url - if( queryTuple.indexOf("project") > -1) { - projectPath = queryTuple.split("=")[1]; - } - }); - } else { - const type_id = this._gid.split(':').join('/'); - projectPath = `${this._groupId}/${type_id}`; - } + + // if exist a global initConfig + this._initConfig = window.initConfig; + + + let projectPath; + + // DEPRECATED: will be removed after v4.0 + + const locationsearch = url ? url.split('?')[1] : location.search.substring(1); + + if (locationsearch) { + //check if exist project in url + /** + * The way to extract project group,type and id + * Example http:localhost:3000/?project=3003/qdjango/1 + * is deprecate + */ + locationsearch.split('&').forEach(queryTuple => { + projectPath = queryTuple.indexOf("project") > -1 ? queryTuple.split("=")[1] : projectPath; + }); + + /////////////////////////////////////////////////////////////////// + + } else if (this._gid) { + projectPath = `${this._groupId}/${this._gid.split(':').join('/')}`; + } + + try { if (projectPath) { - const url = `${host || ''}${this.baseurl}${this._initConfigUrl}/${projectPath}`; - // get configuration from server (return a promise) - try { - const initConfig = await this.getInitConfig(url); - //group, mediaurl, staticurl, user - initConfig.staticurl = "../dist/"; // in development force asset - initConfig.clienturl = "../dist/"; // in development force asset - this._initConfig = initConfig; - // set initConfig - window.initConfig = initConfig; - this.setInitVendorKeys(initConfig); - return initConfig; - } catch(error) { - return Promise.reject(error); - } finally { - this.emit('initconfig', initConfig) - } + // get configuration from server + this._initConfig = await this.getInitConfig(`${host || ''}${this.baseurl}${this._initConfigUrl}/${projectPath}`); } + } catch(error) { + return Promise.reject(error); + } finally { + window.initConfig = this._initConfig; + this.emit('initconfig', this._initConfig); + this.setInitVendorKeys(this._initConfig); + return Promise.resolve(this._initConfig); } };