diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..a877e24 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,19 @@ +# Set the default behavior, in case people don't have core.autocrlf set. +* text=auto + +# Explicitly declare text files you want to always be normalized and converted +# to native line endings on checkout. +*.ts text eol=lf +*.js text eol=lf +*.css text eol=lf +*.scss text eol=lf +*.json text eol=lf +*.xml text eol=lf +*.md text eol=lf +*.gitattributes eol=lf +*.gitignore eol=lf + +# Denote all files that are truly binary and should not be modified. +*.png binary +*.jpg binary +*.gif binary diff --git a/Gruntfile.js b/Gruntfile.js index 916cdfa..7b0b454 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -1,18 +1,29 @@ "use strict"; +const webpack = require("webpack"); const webpackConfig = require("./webpack.config"); -const webpackConfigRelease = {}; -Object.assign(webpackConfigRelease, webpackConfig, { - devtool: false +const webpackConfigRelease = []; + +webpackConfig.forEach(function (currentWebpackConfig) { + const webpackLoaderOptionsPlugin = currentWebpackConfig.plugins.slice(0); + const configRelease = {}; + + webpackLoaderOptionsPlugin.push(new webpack.optimize.UglifyJsPlugin()); + Object.assign(configRelease, currentWebpackConfig, { + devtool: false, + plugins: webpackLoaderOptionsPlugin + }); + webpackConfigRelease.push(configRelease) }); -module.exports = function (grunt) { - var pkg = grunt.file.readJSON("package.json"); + +module.exports = function(grunt) { + const pkg = grunt.file.readJSON("package.json"); grunt.initConfig({ watch: { updateWidgetFiles: { files: [ "./dist/tmp/src/**/*" ], - tasks: [ "webpack:develop", "compress:dist", "copy:distDeployment", "copy:mpk" ], + tasks: [ "webpack:develop", "file_append", "compress:dist", "copy:distDeployment", "copy:mpk" ], options: { debounceDelay: 250, livereload: true @@ -66,7 +77,14 @@ module.exports = function (grunt) { } ] } }, - + file_append: { + addSourceURL: { + files: [ { + append: "\n\n//# sourceURL=PullToRefresh.webmodeler.js\n", + input: "dist/tmp/src/PullToRefresh/widget/PullToRefresh.webmodeler.js" + } ] + } + }, webpack: { develop: webpackConfig, release: webpackConfigRelease @@ -91,18 +109,19 @@ module.exports = function (grunt) { grunt.loadNpmTasks("grunt-contrib-compress"); grunt.loadNpmTasks("grunt-contrib-copy"); grunt.loadNpmTasks("grunt-contrib-watch"); + grunt.loadNpmTasks("grunt-file-append"); grunt.loadNpmTasks("grunt-webpack"); grunt.registerTask("default", [ "clean build", "watch" ]); grunt.registerTask( "clean build", - "Compiles all the assets and copies the files to the dist directory.", - [ "checkDependencies", "clean:build", "webpack:develop", "compress:dist", "copy:mpk" ] + "Compiles all the assets and copies the files to dist build directory.", + [ "checkDependencies", "clean:build", "webpack:develop", "file_append", "compress:dist", "copy:mpk" ] ); grunt.registerTask( "release", "Compiles all the assets and copies the files to the dist directory. Minified without source mapping", - [ "checkDependencies", "clean:build", "webpack:release", "compress:dist", "copy:mpk" ] + [ "checkDependencies", "clean:build", "webpack:release", "file_append", "compress:dist", "copy:mpk" ] ); grunt.registerTask("build", [ "clean build" ]); }; diff --git a/README.md b/README.md index 354783d..3fbd30c 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,7 @@ Mendix version 6.10 or up ## Demo project https://pulltorefresh.mxapps.io -![1](assets/demo.gif) +![1](https://raw.githubusercontent.com/mendixlabs/pull-to-refresh/v1.0.1/assets/demo.gif) ## Usage * Place the widget on a page or layout @@ -48,7 +48,7 @@ To set up the development environment, run: Create a folder named `dist` in the project root. -Create a Mendix test project in the dist folder and rename its root folder to `dist/MxTestProject`. Or get the test project from https://github.com/mendixlabs/pull-to-refresh/releases/download/1.0.0/TestPullToRefresh.mpk Changes to the widget code shall be automatically pushed to this test project. +Create a Mendix test project in the dist folder and rename its root folder to `dist/MxTestProject`. Or get the test project from https://github.com/mendixlabs/pull-to-refresh/releases/download/1.0.1/TestPullToRefresh.mpk Changes to the widget code shall be automatically pushed to this test project. To automatically compile, bundle and push code changes to the running test project, run: diff --git a/package.json b/package.json index a09bb6b..0a5931a 100644 --- a/package.json +++ b/package.json @@ -1,8 +1,17 @@ { "name": "pulltorefresh", "widgetName": "PullToRefresh", - "version": "1.0.0", + "version": "1.0.1", "description": "Use touch on mobile enabling pull down to reload a page", + "copyright": "Mendix BV", + "scripts": { + "pretest": "tsc", + "lint": "tslint -c tslint.json '**/*.ts' '**/*.tsx' --exclude '**/node_modules/**'" + }, + "pre-commit": [ + "pretest", + "lint" + ], "repository": { "type": "git", "url": "https://github.com/mendixlabs/pull-to-refresh" @@ -14,6 +23,8 @@ "devDependencies": { "@types/big.js": "0.0.31", "@types/dojo": "^1.9.35", + "@types/react": "^15.0.16", + "@types/react-dom": "^0.14.23", "check-dependencies": "^1.0.1", "copy-webpack-plugin": "^4.0.1", "css-loader": "^0.27.3", @@ -24,16 +35,22 @@ "grunt-contrib-compress": "^1.4.1", "grunt-contrib-copy": "^1.0.0", "grunt-contrib-watch": "^1.0.0", + "grunt-file-append": "0.0.7", "grunt-newer": "^1.1.1", "grunt-webpack": "^2.0.1", "json-loader": "^0.5.4", "mendix-client": "git+https://github.com/FlockOfBirds/Mendix-client-typing.git", + "pre-commit": "^1.2.2", + "raw-loader": "^0.5.1", + "react": "^15.4.2", + "react-dom": "^15.4.2", "source-map-loader": "^0.2.0", "style-loader": "^0.13.2", "ts-loader": "^2.0.1", - "tslint": "^4.5.1", - "tslint-eslint-rules": "^3.3.0", + "tslint": "^5.4.2", + "tslint-eslint-rules": "^4.1.1", "typescript": "^2.2.1", + "url-loader": "^0.5.8", "webpack": "^2.2.1", "webpack-dev-server": "^2.2.0" } diff --git a/src/PullToRefresh/widget/PullToRefresh.ts b/src/PullToRefresh/widget/PullToRefresh.ts index 6acf88f..a6a9bba 100644 --- a/src/PullToRefresh/widget/PullToRefresh.ts +++ b/src/PullToRefresh/widget/PullToRefresh.ts @@ -1,106 +1,106 @@ -import * as dojoDeclare from "dojo/_base/declare"; -import * as dojoDom from "dojo/dom"; -import * as domConstruct from "dojo/dom-construct"; -import * as WidgetBase from "mxui/widget/_WidgetBase"; - -import { PullToRefresh } from "./handlers/PullToRefresh"; -import "./ui/PullToRefresh.css"; - -class PullToRefreshWrapper extends WidgetBase { - private pullToRefreshText: string; - private releaseToRefreshText: string; - private refreshText: string; - - private pullToRefreshElement: HTMLElement; - private pullToRefresh: PullToRefresh; - private progressId: number; - - postCreate() { - this.onRefresh = this.onRefresh.bind(this); - this.onSyncFailure = this.onSyncFailure.bind(this); - - if (!window.mx.data.synchronizeOffline && !window.mx.data.synchronizeDataWithFiles) { - domConstruct.create("div", { - class: "alert alert-danger", - innerHTML: "The pull to refresh widget is not compatible with this version of Mendix" - }, this.domNode); - } else { - // We share the refresh element across pages. Else the setup and destroy will conflict - this.setUpWidgetDom(); - this.pullToRefresh.setupEvents(); - } - } - - uninitialize(): boolean { - this.pullToRefresh.removeEvents(); - - return true; - } - - private setUpWidgetDom() { - this.pullToRefreshElement = dojoDom.byId("widget-pull-to-refresh"); - if (!this.pullToRefreshElement) { - this.pullToRefreshElement = domConstruct.create("div", { - class: "pull-to-refresh-pull-to-refresh", - id: "widget-pull-to-refresh", - innerHTML: ` -
-
-
-
-
-
- ` - }, document.body, "first"); - } - - this.pullToRefresh = new PullToRefresh({ - mainElement: document.body, - onRefresh: this.onRefresh, - pullToRefreshElement: this.pullToRefreshElement, - pullToRefreshText: this.pullToRefreshText, - refreshText: this.refreshText, - releaseToRefreshText: this.releaseToRefreshText - }); - } - - // Note; this function is hooking into the Mendix private API, this is subject to change without notice! - // Please do not re-use this. The only supported API is publicly documented at - // https://apidocs.mendix.com/7/client/ - private onRefresh(callback: () => void) { - this.progressId = window.mx.ui.showProgress(null, true); - this.pullToRefresh.removeEvents(); - if (window.mx.data.synchronizeOffline) { - window.mx.data.synchronizeOffline({ fast: false }, () => this.onSyncSuccess(callback), this.onSyncFailure); - } else if (window.mx.data.synchronizeDataWithFiles) { - window.mx.data.synchronizeDataWithFiles(() => this.onSyncSuccess(callback), this.onSyncFailure); - } - } - - private onSyncSuccess(callback: () => void) { - window.mx.ui.reload(() => { - if (this.progressId) { - window.mx.ui.hideProgress(this.progressId); - } - this.pullToRefresh.setupEvents(); - if (callback) callback(); - }); - } - - private onSyncFailure() { - if (this.progressId) window.mx.ui.hideProgress(this.progressId); - window.mx.ui.info(window.mx.ui.translate("mxui.sys.UI", "sync_error"), true); - this.pullToRefresh.setupEvents(); - } -} - -// tslint:disable : only-arrow-functions -dojoDeclare("PullToRefresh.widget.PullToRefresh", [ WidgetBase ], function(Source: any) { - const result: any = {}; - for (const property in Source.prototype) { - if (property !== "constructor" && Source.prototype.hasOwnProperty(property)) { - result[property] = Source.prototype[property]; - } - } - return result; -}(PullToRefreshWrapper)); +import * as dojoDeclare from "dojo/_base/declare"; +import * as dojoDom from "dojo/dom"; +import * as domConstruct from "dojo/dom-construct"; +import * as WidgetBase from "mxui/widget/_WidgetBase"; + +import { PullToRefresh } from "./handlers/PullToRefresh"; +import "./ui/PullToRefresh.css"; + +class PullToRefreshWrapper extends WidgetBase { + private pullToRefreshText: string; + private releaseToRefreshText: string; + private refreshText: string; + + private pullToRefreshElement: HTMLElement; + private pullToRefresh: PullToRefresh; + private progressId: number; + + postCreate() { + this.onRefresh = this.onRefresh.bind(this); + this.onSyncFailure = this.onSyncFailure.bind(this); + + if (!window.mx.data.synchronizeOffline && !window.mx.data.synchronizeDataWithFiles) { + domConstruct.create("div", { + class: "alert alert-danger", + innerHTML: "The pull to refresh widget is not compatible with this version of Mendix" + }, this.domNode); + } else { + // We share the refresh element across pages. Else the setup and destroy will conflict + this.setUpWidgetDom(); + this.pullToRefresh.setupEvents(); + } + } + + uninitialize(): boolean { + this.pullToRefresh.removeEvents(); + + return true; + } + + private setUpWidgetDom() { + this.pullToRefreshElement = dojoDom.byId("widget-pull-to-refresh"); + if (!this.pullToRefreshElement) { + this.pullToRefreshElement = domConstruct.create("div", { + class: "pull-to-refresh-pull-to-refresh", + id: "widget-pull-to-refresh", + innerHTML: ` +
+
+
+
+
+
+ ` + }, document.body, "first"); + } + + this.pullToRefresh = new PullToRefresh({ + mainElement: document.body, + onRefresh: this.onRefresh, + pullToRefreshElement: this.pullToRefreshElement, + pullToRefreshText: this.pullToRefreshText, + refreshText: this.refreshText, + releaseToRefreshText: this.releaseToRefreshText + }); + } + + // Note; this function is hooking into the Mendix private API, this is subject to change without notice! + // Please do not re-use this. The only supported API is publicly documented at + // https://apidocs.mendix.com/7/client/ + private onRefresh(callback: () => void) { + this.progressId = window.mx.ui.showProgress(null, true); + this.pullToRefresh.removeEvents(); + if (window.mx.data.synchronizeOffline) { + window.mx.data.synchronizeOffline({ fast: false }, () => this.onSyncSuccess(callback), this.onSyncFailure); + } else if (window.mx.data.synchronizeDataWithFiles) { + window.mx.data.synchronizeDataWithFiles(() => this.onSyncSuccess(callback), this.onSyncFailure); + } + } + + private onSyncSuccess(callback: () => void) { + window.mx.ui.reload(() => { + if (this.progressId) { + window.mx.ui.hideProgress(this.progressId); + } + this.pullToRefresh.setupEvents(); + if (callback) callback(); + }); + } + + private onSyncFailure() { + if (this.progressId) window.mx.ui.hideProgress(this.progressId); + window.mx.ui.info(window.mx.ui.translate("mxui.sys.UI", "sync_error"), true); + this.pullToRefresh.setupEvents(); + } +} + +// tslint:disable : only-arrow-functions +dojoDeclare("PullToRefresh.widget.PullToRefresh", [ WidgetBase ], function(Source: any) { + const result: any = {}; + for (const property in Source.prototype) { + if (property !== "constructor" && Source.prototype.hasOwnProperty(property)) { + result[property] = Source.prototype[property]; + } + } + return result; +}(PullToRefreshWrapper)); diff --git a/src/PullToRefresh/widget/PullToRefresh.webmodeler.ts b/src/PullToRefresh/widget/PullToRefresh.webmodeler.ts new file mode 100644 index 0000000..0e04818 --- /dev/null +++ b/src/PullToRefresh/widget/PullToRefresh.webmodeler.ts @@ -0,0 +1,13 @@ +import { Component, DOM } from "react"; + +declare function require(name: string): string; + +// tslint:disable-next-line class-name +export class preview extends Component<{}, {}> { + + render() { + const image = require("./ui/preview.png"); + + return DOM.img({ src: image }); + } +} diff --git a/src/PullToRefresh/widget/handlers/PullToRefresh.ts b/src/PullToRefresh/widget/handlers/PullToRefresh.ts index b235633..9aceefe 100644 --- a/src/PullToRefresh/widget/handlers/PullToRefresh.ts +++ b/src/PullToRefresh/widget/handlers/PullToRefresh.ts @@ -85,8 +85,17 @@ export class PullToRefresh { } private update(nextState: State) { - const { iconElement, textElement, pullToRefreshElement, classPrefix, iconRefreshing, iconArrow, - refreshText, pullToRefreshText, releaseToRefreshText, reloadDistance } = this.settings; + const { + iconElement, + textElement, + pullToRefreshElement, + classPrefix, + iconRefreshing, + iconArrow, + refreshText, + pullToRefreshText, + releaseToRefreshText, + reloadDistance } = this.settings; if (iconElement && textElement && nextState !== this.state ) { if (nextState === "refreshing") { diff --git a/src/PullToRefresh/widget/ui/preview.png b/src/PullToRefresh/widget/ui/preview.png new file mode 100644 index 0000000..39c9b33 Binary files /dev/null and b/src/PullToRefresh/widget/ui/preview.png differ diff --git a/src/package.xml b/src/package.xml index bdc940d..4727599 100644 --- a/src/package.xml +++ b/src/package.xml @@ -1,6 +1,6 @@ - + diff --git a/tslint.json b/tslint.json index e7c0265..b99eeb9 100644 --- a/tslint.json +++ b/tslint.json @@ -1,90 +1,150 @@ { - "rulesDirectory": [ "node_modules/tslint-eslint-rules/dist/rules" ], - "extends": "tslint:latest", - "rules": { - "class-name": true, - "comment-format": [ true, "check-space" ], - "eofline": true, - "indent": [ true, "spaces" ], - "member-ordering": [ true, "variables-before-functions" ], - "no-arg": true, - "no-bitwise": true, - "no-conditional-assignment": true, - "no-console": [ true ], - "no-debugger": true, - "no-duplicate-key": true, - "no-duplicate-variable": true, - "no-eval": true, - "no-inferrable-types": [ true ], - "no-internal-module": true, - "no-shadowed-variable": true, - "no-switch-case-fall-through": true, - "no-trailing-whitespace": true, - "no-unreachable": true, - "no-unused-expression": true, - "no-var-keyword": true, - "one-line": [ - true, - "check-catch", - "check-else", - "check-finally", - "check-open-brace", - "check-whitespace" - ], - "quotemark": [ true, "double" ], - "semicolon": [ true, "always" ], - "triple-equals": [ true, "allow-null-check" ], - "typedef-whitespace": [ - true, { - "call-signature": "nospace", - "index-signature": "nospace", - "parameter": "nospace", - "property-declaration": "nospace", - "variable-declaration": "nospace" - } - ], - "variable-name": [ true, "ban-keywords" ], - "whitespace": [ - true, - "check-branch", - "check-decl", - "check-operator", - "check-module", - "check-separator", - "check-type" - ], - "interface-name": [ false, "always-prefix" ], - "no-multi-spaces": [ true ], - "no-extra-semi": true, - "no-irregular-whitespace": true, - "no-duplicate-case": true, - "no-control-regex": true, - "no-empty-character-class": true, - "no-ex-assign": true, - "no-extra-boolean-cast": true, - "no-unexpected-multiline": true, - "no-sparse-arrays": true, - "no-regex-spaces": true, - "no-invalid-regexp": true, - "valid-typeof": true, - "array-bracket-spacing": [ true, "always" ], - "block-spacing": [ true, "always" ], - "brace-style": [ - true, - "1tbs", { - "allowSingleLine": true - } - ], - "object-curly-spacing": [ true, "always" ], - "trailing-comma": [ true, { "multiline": "never", "singleline": "never" } ], - "member-access": false, - "no-unused-variable": [ true, "react" ], - "arrow-parens": false, - "curly": false - }, - "jsRules": { - "trailing-comma": [ false ], - "object-literal-sort-keys": false, - "array-bracket-spacing": [ true, "always" ] - } + "rulesDirectory": [ + "node_modules/tslint-eslint-rules/dist/rules" + ], + "extends": "tslint:latest", + "rules": { + "class-name": true, + "comment-format": [ + true, + "check-space" + ], + "eofline": true, + "indent": [ + true, + "spaces" + ], + "member-ordering": [ + true, + "variables-before-functions" + ], + "no-arg": true, + "no-bitwise": true, + "no-conditional-assignment": true, + "no-console": [ + true + ], + "no-debugger": true, + "no-duplicate-variable": true, + "no-eval": true, + "no-inferrable-types": [ + true + ], + "no-internal-module": true, + "no-shadowed-variable": true, + "no-switch-case-fall-through": true, + "no-trailing-whitespace": true, + "no-unused-expression": true, + "no-var-keyword": true, + "one-line": [ + true, + "check-catch", + "check-else", + "check-finally", + "check-open-brace", + "check-whitespace" + ], + "quotemark": [ + true, + "double" + ], + "semicolon": [ + true, + "always" + ], + "triple-equals": [ + true, + "allow-null-check" + ], + "typedef-whitespace": [ + true, + { + "call-signature": "nospace", + "index-signature": "nospace", + "parameter": "nospace", + "property-declaration": "nospace", + "variable-declaration": "nospace" + } + ], + "variable-name": [ + true, + "ban-keywords" + ], + "whitespace": [ + true, + "check-branch", + "check-decl", + "check-operator", + "check-module", + "check-separator", + "check-type" + ], + "interface-name": [ + false, + "always-prefix" + ], + "no-multi-spaces": [ + true + ], + "no-extra-semi": true, + "no-irregular-whitespace": true, + "no-duplicate-case": true, + "no-control-regex": true, + "no-empty-character-class": true, + "no-ex-assign": true, + "no-extra-boolean-cast": true, + "no-unexpected-multiline": true, + "no-sparse-arrays": true, + "no-regex-spaces": true, + "no-invalid-regexp": true, + "valid-typeof": true, + "array-bracket-spacing": [ + true, + "always" + ], + "block-spacing": [ + true, + "always" + ], + "brace-style": [ + true, + "1tbs", + { + "allowSingleLine": true + } + ], + "object-curly-spacing": [ + true, + "always" + ], + "trailing-comma": [ + true, + { + "multiline": "never", + "singleline": "never" + } + ], + "member-access": false, + "ordered-imports": [ + true, + { + "import-sources-order": "any", + "named-imports-order": "lowercase-last" + } + ], + "curly": false, + "arrow-parens": false, + "linebreak-style": [ true, "LF" ] + }, + "jsRules": { + "trailing-comma": [ + false + ], + "object-literal-sort-keys": false, + "array-bracket-spacing": [ + true, + "always" + ], + "linebreak-style": [ true, "LF" ] + } } diff --git a/webpack.config.js b/webpack.config.js index 8767c0a..b7237c5 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -3,15 +3,15 @@ const path = require("path"); const CopyWebpackPlugin = require("copy-webpack-plugin"); const ExtractTextPlugin = require("extract-text-webpack-plugin"); -module.exports = { +const widgetConfig = { entry: "./src/PullToRefresh/widget/PullToRefresh.ts", output: { path: path.resolve(__dirname, "dist/tmp"), filename: "src/PullToRefresh/widget/PullToRefresh.js", - libraryTarget: "umd" + libraryTarget: "amd" }, resolve: { - extensions: [ ".ts", ".js", ".json" ] + extensions: [ ".ts" ] }, module: { rules: [ @@ -19,17 +19,16 @@ module.exports = { { test: /\.css$/, loader: ExtractTextPlugin.extract({ fallback: "style-loader", use: "css-loader" - }) } + }) }, + { test: /\.(png|jpeg)$/, loader: "url-loader", options: { limit: 8192 } } ] }, devtool: "source-map", - externals: [ - "mxui/widget/_WidgetBase", "dojo/_base/declare", "dojo/dom-construct", "dojo/dom", "dojo/dom-class", - "dojo/dom-style" ], + externals: [ "react", "react-dom", /^mxui\/|^mendix\/|^dojo\/|^dijit\// ], plugins: [ new CopyWebpackPlugin([ - { from: "src/**/*.js" }, { from: "src/**/*.xml" }, + { from: "src/**/*.png" }, { from: "assets/Preview.png", to: "src/PullToRefresh/widget/Preview.png"} ], { copyUnmodified: true @@ -41,3 +40,29 @@ module.exports = { }) ] }; + +const previewConfig = { + entry: "./src/PullToRefresh/widget/PullToRefresh.webmodeler.ts", + output: { + path: path.resolve(__dirname, "dist/tmp"), + filename: "src/PullToRefresh/widget/PullToRefresh.webmodeler.js", + libraryTarget: "commonjs" + }, + resolve: { + extensions: [ ".ts" ] + }, + module: { + rules: [ + { test: /\.ts$/, use: "ts-loader" }, + { test: /\.css$/, loader: "raw-loader" }, + { test: /\.(png|jpeg)$/, loader: "url-loader", options: { limit: 8192 } } + ] + }, + devtool: "inline-source-map", + externals: [ "react", "react-dom" ], + plugins: [ + new webpack.LoaderOptionsPlugin({ debug: true }) + ] +}; + +module.exports = [ widgetConfig, previewConfig ];