diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..9e179ef --- /dev/null +++ b/.gitignore @@ -0,0 +1,19 @@ +.DS_Store +node_modules/ +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# Editor directories and files +.idea +.vscode +*.suo +*.ntvs* +*.njsproj +*.sln + +.marginalia + +js/ +build/ +coverage/ \ No newline at end of file diff --git a/Makefile b/Makefile index f14fa66..4806724 100644 --- a/Makefile +++ b/Makefile @@ -1,51 +1,52 @@ -app_name=gadgetbridge -project_dir=$(CURDIR)/../$(app_name) -build_dir=$(CURDIR)/build/artifacts -appstore_dir=$(build_dir)/appstore -source_dir=$(build_dir)/source -sign_dir=$(build_dir)/sign -package_name=$(app_name) -cert_dir=$(HOME)/.nextcloud/certificates -version+=1.0.0 +all: dev-setup lint build-js-production test -all: appstore +# Dev env management +dev-setup: clean clean-dev npm-init -release: appstore create-tag +npm-init: + yarn ci -create-tag: - git tag -s -a v$(version) -m "Tagging the $(version) release." - git push origin v$(version) +npm-update: + yarn update +# Building +build-js: + yarn run dev + +build-js-production: + yarn run build + +watch-js: + yarn run watch + +# Testing +test: + yarn run test + +test-watch: + yarn run test:watch + +test-coverage: + yarn run test:coverage + +# Linting +lint: + yarn run lint + +lint-fix: + yarn run lint:fix + +# Style linting +stylelint: + yarn run stylelint + +stylelint-fix: + yarn run stylelint:fix + +# Cleaning clean: - rm -rf $(build_dir) - rm -rf node_modules - -appstore: clean - mkdir -p $(sign_dir) - rsync -a \ - --exclude=/build \ - --exclude=/docs \ - --exclude=/l10n/templates \ - --exclude=/l10n/.tx \ - --exclude=/tests \ - --exclude=/.git \ - --exclude=/screenshots \ - --exclude=/.github \ - --exclude=/l10n/l10n.pl \ - --exclude=/CONTRIBUTING.md \ - --exclude=/issue_template.md \ - --exclude=/README.md \ - --exclude=/.gitattributes \ - --exclude=/.gitignore \ - --exclude=/.scrutinizer.yml \ - --exclude=/.travis.yml \ - --exclude=/.drone.yml \ - --exclude=/Makefile \ - $(project_dir)/ $(sign_dir)/$(app_name) - tar -czf $(build_dir)/$(app_name)-$(version).tar.gz \ - -C $(sign_dir) $(app_name) - @if [ -f $(cert_dir)/$(app_name).key ]; then \ - echo "Signing packageā€¦"; \ - openssl dgst -sha512 -sign $(cert_dir)/$(app_name).key $(build_dir)/$(app_name)-$(version).tar.gz | openssl base64; \ - fi + rm -f js/* + +clean-dev: + rm -rf node_modules \ No newline at end of file diff --git a/appinfo/info.xml b/appinfo/info.xml index 75d26a7..c9a26a0 100644 --- a/appinfo/info.xml +++ b/appinfo/info.xml @@ -18,7 +18,7 @@ GadgetBridge - + diff --git a/babel.config.js b/babel.config.js new file mode 100644 index 0000000..7a5d71e --- /dev/null +++ b/babel.config.js @@ -0,0 +1,11 @@ +module.exports = { + plugins: ['@babel/plugin-syntax-dynamic-import'], + presets: [ + [ + '@babel/preset-env', + { + modules: false + } + ] + ] +} diff --git a/js/gadgetbridge.js b/js/gadgetbridge.js index fb244f4..a0ae04c 100644 --- a/js/gadgetbridge.js +++ b/js/gadgetbridge.js @@ -1,226 +1,58734 @@ +/******/ (function(modules) { // webpackBootstrap +/******/ // The module cache +/******/ var installedModules = {}; +/******/ +/******/ // The require function +/******/ function __webpack_require__(moduleId) { +/******/ +/******/ // Check if module is in cache +/******/ if(installedModules[moduleId]) { +/******/ return installedModules[moduleId].exports; +/******/ } +/******/ // Create a new module (and put it into the cache) +/******/ var module = installedModules[moduleId] = { +/******/ i: moduleId, +/******/ l: false, +/******/ exports: {} +/******/ }; +/******/ +/******/ // Execute the module function +/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); +/******/ +/******/ // Flag the module as loaded +/******/ module.l = true; +/******/ +/******/ // Return the exports of the module +/******/ return module.exports; +/******/ } +/******/ +/******/ +/******/ // expose the modules object (__webpack_modules__) +/******/ __webpack_require__.m = modules; +/******/ +/******/ // expose the module cache +/******/ __webpack_require__.c = installedModules; +/******/ +/******/ // define getter function for harmony exports +/******/ __webpack_require__.d = function(exports, name, getter) { +/******/ if(!__webpack_require__.o(exports, name)) { +/******/ Object.defineProperty(exports, name, { enumerable: true, get: getter }); +/******/ } +/******/ }; +/******/ +/******/ // define __esModule on exports +/******/ __webpack_require__.r = function(exports) { +/******/ if(typeof Symbol !== 'undefined' && Symbol.toStringTag) { +/******/ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' }); +/******/ } +/******/ Object.defineProperty(exports, '__esModule', { value: true }); +/******/ }; +/******/ +/******/ // create a fake namespace object +/******/ // mode & 1: value is a module id, require it +/******/ // mode & 2: merge all properties of value into the ns +/******/ // mode & 4: return value when already ns object +/******/ // mode & 8|1: behave like require +/******/ __webpack_require__.t = function(value, mode) { +/******/ if(mode & 1) value = __webpack_require__(value); +/******/ if(mode & 8) return value; +/******/ if((mode & 4) && typeof value === 'object' && value && value.__esModule) return value; +/******/ var ns = Object.create(null); +/******/ __webpack_require__.r(ns); +/******/ Object.defineProperty(ns, 'default', { enumerable: true, value: value }); +/******/ if(mode & 2 && typeof value != 'string') for(var key in value) __webpack_require__.d(ns, key, function(key) { return value[key]; }.bind(null, key)); +/******/ return ns; +/******/ }; +/******/ +/******/ // getDefaultExport function for compatibility with non-harmony modules +/******/ __webpack_require__.n = function(module) { +/******/ var getter = module && module.__esModule ? +/******/ function getDefault() { return module['default']; } : +/******/ function getModuleExports() { return module; }; +/******/ __webpack_require__.d(getter, 'a', getter); +/******/ return getter; +/******/ }; +/******/ +/******/ // Object.prototype.hasOwnProperty.call +/******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); }; +/******/ +/******/ // __webpack_public_path__ +/******/ __webpack_require__.p = "/js/"; +/******/ +/******/ +/******/ // Load entry module and return exports +/******/ return __webpack_require__(__webpack_require__.s = "./src/main.js"); +/******/ }) +/************************************************************************/ +/******/ ({ + +/***/ "./node_modules/@nextcloud/dialogs/dist/filepicker.js": +/*!************************************************************!*\ + !*** ./node_modules/@nextcloud/dialogs/dist/filepicker.js ***! + \************************************************************/ +/*! no static exports found */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +__webpack_require__(/*! core-js/modules/es.object.to-string */ "./node_modules/core-js/modules/es.object.to-string.js"); + +__webpack_require__(/*! core-js/modules/es.promise */ "./node_modules/core-js/modules/es.promise.js"); + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.getFilePickerBuilder = getFilePickerBuilder; +exports.FilePickerBuilder = exports.FilePicker = exports.FilePickerType = void 0; + +function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + +function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } + +function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; } + +/// +var FilePickerType; +exports.FilePickerType = FilePickerType; + +(function (FilePickerType) { + FilePickerType[FilePickerType["Choose"] = 1] = "Choose"; + FilePickerType[FilePickerType["Move"] = 2] = "Move"; + FilePickerType[FilePickerType["Copy"] = 3] = "Copy"; + FilePickerType[FilePickerType["CopyMove"] = 4] = "CopyMove"; +})(FilePickerType || (exports.FilePickerType = FilePickerType = {})); + +var FilePicker = /*#__PURE__*/function () { + function FilePicker(title, multiSelect, mimeTypeFilter, modal, type, directoriesAllowed, path) { + _classCallCheck(this, FilePicker); + + this.title = title; + this.multiSelect = multiSelect; + this.mimeTypeFiler = mimeTypeFilter; + this.modal = modal; + this.type = type; + this.directoriesAllowed = directoriesAllowed; + this.path = path; + } + + _createClass(FilePicker, [{ + key: "pick", + value: function pick() { + var _this = this; + + return new Promise(function (res, rej) { + OC.dialogs.filepicker(_this.title, res, _this.multiSelect, _this.mimeTypeFiler, _this.modal, _this.type, _this.path, { + allowDirectoryChooser: _this.directoriesAllowed + }); + }); + } + }]); + + return FilePicker; +}(); + +exports.FilePicker = FilePicker; + +var FilePickerBuilder = /*#__PURE__*/function () { + function FilePickerBuilder(title) { + _classCallCheck(this, FilePickerBuilder); + + this.multiSelect = false; + this.mimeTypeFiler = []; + this.modal = true; + this.type = FilePickerType.Choose; + this.directoriesAllowed = false; + this.title = title; + } + + _createClass(FilePickerBuilder, [{ + key: "setMultiSelect", + value: function setMultiSelect(ms) { + this.multiSelect = ms; + return this; + } + }, { + key: "addMimeTypeFilter", + value: function addMimeTypeFilter(filter) { + this.mimeTypeFiler.push(filter); + return this; + } + }, { + key: "setMimeTypeFilter", + value: function setMimeTypeFilter(filter) { + this.mimeTypeFiler = filter; + return this; + } + }, { + key: "setModal", + value: function setModal(modal) { + this.modal = modal; + return this; + } + }, { + key: "setType", + value: function setType(type) { + this.type = type; + return this; + } + }, { + key: "allowDirectories", + value: function allowDirectories() { + var allow = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : true; + this.directoriesAllowed = allow; + return this; + } + }, { + key: "startAt", + value: function startAt(path) { + this.path = path; + return this; + } + }, { + key: "build", + value: function build() { + return new FilePicker(this.title, this.multiSelect, this.mimeTypeFiler, this.modal, this.type, this.directoriesAllowed, this.path); + } + }]); + + return FilePickerBuilder; +}(); + +exports.FilePickerBuilder = FilePickerBuilder; + +function getFilePickerBuilder(title) { + return new FilePickerBuilder(title); +} +//# sourceMappingURL=filepicker.js.map + +/***/ }), + +/***/ "./node_modules/@nextcloud/dialogs/dist/index.js": +/*!*******************************************************!*\ + !*** ./node_modules/@nextcloud/dialogs/dist/index.js ***! + \*******************************************************/ +/*! no static exports found */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", { + value: true +}); +Object.defineProperty(exports, "FilePicker", { + enumerable: true, + get: function get() { + return _filepicker.FilePicker; + } +}); +Object.defineProperty(exports, "FilePickerBuilder", { + enumerable: true, + get: function get() { + return _filepicker.FilePickerBuilder; + } +}); +Object.defineProperty(exports, "getFilePickerBuilder", { + enumerable: true, + get: function get() { + return _filepicker.getFilePickerBuilder; + } +}); +Object.defineProperty(exports, "showMessage", { + enumerable: true, + get: function get() { + return _toast.showMessage; + } +}); +Object.defineProperty(exports, "showSuccess", { + enumerable: true, + get: function get() { + return _toast.showSuccess; + } +}); +Object.defineProperty(exports, "showWarning", { + enumerable: true, + get: function get() { + return _toast.showWarning; + } +}); +Object.defineProperty(exports, "showInfo", { + enumerable: true, + get: function get() { + return _toast.showInfo; + } +}); +Object.defineProperty(exports, "showError", { + enumerable: true, + get: function get() { + return _toast.showError; + } +}); + +var _filepicker = __webpack_require__(/*! ./filepicker */ "./node_modules/@nextcloud/dialogs/dist/filepicker.js"); + +var _toast = __webpack_require__(/*! ./toast */ "./node_modules/@nextcloud/dialogs/dist/toast.js"); +//# sourceMappingURL=index.js.map + +/***/ }), + +/***/ "./node_modules/@nextcloud/dialogs/dist/toast.js": +/*!*******************************************************!*\ + !*** ./node_modules/@nextcloud/dialogs/dist/toast.js ***! + \*******************************************************/ +/*! no static exports found */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +__webpack_require__(/*! core-js/modules/es.symbol */ "./node_modules/core-js/modules/es.symbol.js"); + +__webpack_require__(/*! core-js/modules/es.array.filter */ "./node_modules/core-js/modules/es.array.filter.js"); + +__webpack_require__(/*! core-js/modules/es.array.for-each */ "./node_modules/core-js/modules/es.array.for-each.js"); + +__webpack_require__(/*! core-js/modules/es.object.assign */ "./node_modules/core-js/modules/es.object.assign.js"); + +__webpack_require__(/*! core-js/modules/es.object.get-own-property-descriptor */ "./node_modules/core-js/modules/es.object.get-own-property-descriptor.js"); + +__webpack_require__(/*! core-js/modules/es.object.get-own-property-descriptors */ "./node_modules/core-js/modules/es.object.get-own-property-descriptors.js"); + +__webpack_require__(/*! core-js/modules/es.object.keys */ "./node_modules/core-js/modules/es.object.keys.js"); + +__webpack_require__(/*! core-js/modules/web.dom-collections.for-each */ "./node_modules/core-js/modules/web.dom-collections.for-each.js"); + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.showMessage = showMessage; +exports.showError = showError; +exports.showWarning = showWarning; +exports.showInfo = showInfo; +exports.showSuccess = showSuccess; + +var _toastifyJs = _interopRequireDefault(__webpack_require__(/*! toastify-js */ "./node_modules/toastify-js/src/toastify.js")); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); if (enumerableOnly) symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; }); keys.push.apply(keys, symbols); } return keys; } + +function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; if (i % 2) { ownKeys(Object(source), true).forEach(function (key) { _defineProperty(target, key, source[key]); }); } else if (Object.getOwnPropertyDescriptors) { Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)); } else { ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } } return target; } + +function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } + +function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + +var ToastType = function ToastType() { + _classCallCheck(this, ToastType); +}; + +ToastType.ERROR = 'toast-error'; +ToastType.WARNING = 'toast-warning'; +ToastType.INFO = 'toast-info'; +ToastType.SUCCESS = 'toast-success'; +ToastType.PERMANENT = 'toast-error'; + +/** + * Show a toast message + * + * @param text Message to be shown in the toast, any HTML is removed by default + * @param options + */ +function showMessage(text, options) { + var _options$type; + + options = Object.assign({ + timeout: 7, + isHTML: false, + type: undefined, + // An undefined selector defaults to the body element + selector: undefined, + onRemove: function onRemove() {}, + onClick: function onClick() {}, + close: true + }, options); + + if (!options.isHTML) { + // fime mae sure that text is extracted + var element = document.createElement('div'); + element.innerHTML = text; + text = element.innerText; + } + + var classes = (_options$type = options.type) !== null && _options$type !== void 0 ? _options$type : ''; + var toast = (0, _toastifyJs.default)({ + text: text, + duration: options.timeout === null || options.timeout === undefined ? null : options.timeout * 1000, + callback: options.onRemove, + onClick: options.onClick, + close: options.close, + gravity: 'top', + selector: options.selector, + position: 'right', + backgroundColor: '', + className: 'toast ' + classes + }); + toast.showToast(); + return toast; +} /** - * @copyright (c) 2017 Joas Schilling + * Show a toast message with error styling * - * @author Joas Schilling + * @param text Message to be shown in the toast, any HTML is removed by default + * @param options + */ + + +function showError(text, options) { + return showMessage(text, _objectSpread(_objectSpread({}, options), {}, { + type: ToastType.ERROR + })); +} +/** + * Show a toast message with warning styling * - * This file is licensed under the Affero General Public License version 3 or - * later. See the COPYING file. + * @param text Message to be shown in the toast, any HTML is removed by default + * @param options */ -(function(OC, OCA, _) { - OCA = OCA || {}; - OCA.GadgetBridge = { - databaseFileId: 0, - selectedDevice: 0, - lastRawKind: 0, +function showWarning(text, options) { + return showMessage(text, _objectSpread(_objectSpread({}, options), {}, { + type: ToastType.WARNING + })); +} +/** + * Show a toast message with info styling + * + * @param text Message to be shown in the toast, any HTML is removed by default + * @param options + */ - _deviceTemplate: null, - _deviceHTML: '' + - '
  • ' + - '' + - '' + - '{{NAME}} ({{IDENTIFIER}})' + - '' + - '
  • ', - initialise: function() { - $('#import-data').on('click', _.bind(this._importButtonOnClick, this)); - this._deviceTemplate = Handlebars.compile(this._deviceHTML); - this._selectedDatabase($('#app-content').attr('data-database-id'), $('#app-content').attr('data-database-path')); - }, +function showInfo(text, options) { + return showMessage(text, _objectSpread(_objectSpread({}, options), {}, { + type: ToastType.INFO + })); +} +/** + * Show a toast message with success styling + * + * @param text Message to be shown in the toast, any HTML is removed by default + * @param options + */ - _importButtonOnClick: function(e) { - e.preventDefault(); - OCdialogs.filepicker( - t('gadgetbridge', 'Choose a file to import'), - _.bind(this._filePickerCallback, this) - ) - }, - _selectedDatabase: function(id, path) { - this.databaseFileId = id; - this.databaseFilePath = path; - $('.settings-caption').text(this.databaseFilePath); - $('#app-content').attr('data-database-id', this.databaseFileId); - $('#app-content').attr('data-database-path', this.databaseFilePath); +function showSuccess(text, options) { + return showMessage(text, _objectSpread(_objectSpread({}, options), {}, { + type: ToastType.SUCCESS + })); +} +//# sourceMappingURL=toast.js.map - if (this.databaseFileId > 0) { - this._loadDevices(); - } - }, +/***/ }), - _filePickerCallback: function(path) { - var self = this; - - $.ajax({ - url: OC.linkToOCS('apps/gadgetbridge/api/v1', 2) + 'database', - type: 'POST', - beforeSend: function (request) { - request.setRequestHeader('Accept', 'application/json'); - }, - data: { - path: path - }, - success: function(result) { - self._selectedDatabase( - result.ocs.data.fileId, - path.substring(1) // Remove leading slash - ); - }, - error: function() { - OC.Notification. showTemporary(t('gadgetbridge', 'The selected file is not a readable Gadgetbridge database')); - } - }); - }, +/***/ "./node_modules/@nextcloud/dialogs/styles/toast.scss": +/*!***********************************************************!*\ + !*** ./node_modules/@nextcloud/dialogs/styles/toast.scss ***! + \***********************************************************/ +/*! no static exports found */ +/***/ (function(module, exports, __webpack_require__) { - _loadDevices: function() { - var self = this; - $.ajax({ - url: OC.linkToOCS('apps/gadgetbridge/api/v1', 2) + this.databaseFileId + '/devices', - beforeSend: function (request) { - request.setRequestHeader('Accept', 'application/json'); - }, - success: function(result) { - // TODO Remove previous devices - - - var singleDeviceDatabase = result.ocs.data.length === 1; - - _.each(result.ocs.data, function(device) { - var $device = $(self._deviceTemplate(device)); - $device.on('click', function() { - self.selectedDevice = $(this).attr('data-device-id'); - $(this).addClass('active'); - if (self.selectedDevice !== $(this).attr('data-device-id')) { - self._loadDevice(moment().format('YYYY/MM/DD/HH/mm')); - } - }); - $('#app-navigation ul').append($device); - - if (singleDeviceDatabase) { - self.selectedDevice = device._id; - self._loadDevice(moment().format('YYYY/MM/DD/HH/mm')); - $device.addClass('active'); - } - }); - }, - error: function() { - OC.Notification. showTemporary(t('gadgetbridge', 'The selected file is not a readable Gadgetbridge database')); - } - }); - }, +// style-loader: Adds some css to the DOM by adding a