From 1a62af799b5d5791340e6109885d82b6da1e2e52 Mon Sep 17 00:00:00 2001 From: JC Brand Date: Wed, 1 Dec 2021 11:54:41 +0100 Subject: [PATCH] Refactor converse-desktop and bump to Converse 9 This removes all AngularJS code Angular version 1 is unmaintained and largely obsolete since years. Additionally, I'd rather re-use the conventions and libraries from Converse instead of having different ones in converse-desktop. This means we're losing some functionality, such as the app settings and the about modal. This is unfortunate, but ideally (non-Electron) app settings should be implemented in Converse itself and not in this repo. --- .eslintrc.js | 22 ++++ .../converse.js/3rdparty => 3rdparty}/LICENSE | 0 .../libsignal-protocol.js | 0 Makefile | 16 +++ app/controllers/about-controller.js | 10 -- app/controllers/default-controller.js | 47 -------- app/controllers/footer-controller.js | 7 -- app/controllers/login-controller.js | 21 ---- app/controllers/settings-controller.js | 40 ------- app/converse-plugins/desktop-credentials.js | 24 ++++ app/credentials.js | 49 ++++++++ app/init.js | 14 --- app/services/app-state-service.js | 22 ---- app/services/credentials-service.js | 54 --------- app/services/desktop-service.js | 96 --------------- app/services/settings-service.js | 109 ------------------ app/services/system-service.js | 28 ----- app/services/xmpp-helper-service.js | 25 ---- app/views/about/page.html | 35 ------ app/views/default/page.html | 24 ---- app/views/login/page.html | 43 ------- app/views/settings/page.html | 28 ----- app/views/shared/_footer.html | 4 - index.html | 39 ++----- .../converse-desktop/desktop-plugin.js | 67 ----------- main.js | 62 ++-------- modules/menu-service.js | 44 +------ modules/settings-service.js | 4 +- modules/tray-service.js | 18 ++- package.json | 10 +- renderer.js | 64 ---------- resources/css/app.css | 10 -- resources/css/page-about.css | 39 ------- resources/css/page-login.css | 100 ---------------- resources/images/envelope-16x16.png | Bin 488 -> 0 bytes resources/images/envelope-48x48.png | Bin 1585 -> 0 bytes resources/images/envelopeTemplate.png | Bin 362 -> 0 bytes resources/images/envelopeTemplate@2x.png | Bin 719 -> 0 bytes resources/images/icon-16x16.png | Bin 701 -> 5294 bytes resources/images/icon-48x48.png | Bin 2339 -> 9562 bytes resources/images/iconTemplate.png | Bin 399 -> 0 bytes resources/images/iconTemplate@2x.png | Bin 895 -> 0 bytes resources/images/logo.ico | Bin 6538 -> 1150 bytes resources/images/logo.png | Bin 8038 -> 7321 bytes setup.js | 46 ++++++++ 45 files changed, 193 insertions(+), 1028 deletions(-) create mode 100644 .eslintrc.js rename {libs/converse.js/3rdparty => 3rdparty}/LICENSE (100%) rename {libs/converse.js/3rdparty => 3rdparty}/libsignal-protocol.js (100%) create mode 100644 Makefile delete mode 100644 app/controllers/about-controller.js delete mode 100644 app/controllers/default-controller.js delete mode 100644 app/controllers/footer-controller.js delete mode 100644 app/controllers/login-controller.js delete mode 100644 app/controllers/settings-controller.js create mode 100644 app/converse-plugins/desktop-credentials.js create mode 100644 app/credentials.js delete mode 100644 app/init.js delete mode 100644 app/services/app-state-service.js delete mode 100644 app/services/credentials-service.js delete mode 100644 app/services/desktop-service.js delete mode 100644 app/services/settings-service.js delete mode 100644 app/services/system-service.js delete mode 100644 app/services/xmpp-helper-service.js delete mode 100644 app/views/about/page.html delete mode 100644 app/views/default/page.html delete mode 100644 app/views/login/page.html delete mode 100644 app/views/settings/page.html delete mode 100644 app/views/shared/_footer.html delete mode 100644 libs/converse.js/converse-desktop/desktop-plugin.js delete mode 100644 renderer.js delete mode 100644 resources/css/page-about.css delete mode 100644 resources/css/page-login.css delete mode 100644 resources/images/envelope-16x16.png delete mode 100644 resources/images/envelope-48x48.png delete mode 100644 resources/images/envelopeTemplate.png delete mode 100644 resources/images/envelopeTemplate@2x.png delete mode 100644 resources/images/iconTemplate.png delete mode 100644 resources/images/iconTemplate@2x.png create mode 100644 setup.js diff --git a/.eslintrc.js b/.eslintrc.js new file mode 100644 index 0000000..a746ad4 --- /dev/null +++ b/.eslintrc.js @@ -0,0 +1,22 @@ +module.exports = { + "env": { + "browser": true, + "es2021": true + }, + "extends": "eslint:recommended", + "parserOptions": { + "ecmaVersion": 13, + "sourceType": "module" + }, + "globals": { + "__dirname": true, + "converse": true, + "exports": true, + "module": true, + "process": true, + "require": true, + }, + "rules": { + "prefer-const": "error", + } +}; diff --git a/libs/converse.js/3rdparty/LICENSE b/3rdparty/LICENSE similarity index 100% rename from libs/converse.js/3rdparty/LICENSE rename to 3rdparty/LICENSE diff --git a/libs/converse.js/3rdparty/libsignal-protocol.js b/3rdparty/libsignal-protocol.js similarity index 100% rename from libs/converse.js/3rdparty/libsignal-protocol.js rename to 3rdparty/libsignal-protocol.js diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..8533e98 --- /dev/null +++ b/Makefile @@ -0,0 +1,16 @@ +BIN ?= ./node_modules/.bin/ +ESLINT ?= ./node_modules/.bin/eslint + +node_modules: package.json package-lock.json + npm i + +build: node_modules + $(BIN)/electron-rebuild + +serve: build + npm start + +.PHONY: eslint +eslint: node_modules + $(ESLINT) *.js + $(ESLINT) app/**/*.js diff --git a/app/controllers/about-controller.js b/app/controllers/about-controller.js deleted file mode 100644 index 72366bb..0000000 --- a/app/controllers/about-controller.js +++ /dev/null @@ -1,10 +0,0 @@ -let angApp = require(__dirname+'/../init') - -angApp.controller('AboutController', function($scope, AppStateService, AppInfo) { - - $scope.appInfo = AppInfo - - $scope.closeAbout = () => { - AppStateService.set(AppStateService.APP_STATE_DEFAULT) - } -}) \ No newline at end of file diff --git a/app/controllers/default-controller.js b/app/controllers/default-controller.js deleted file mode 100644 index f2ebe99..0000000 --- a/app/controllers/default-controller.js +++ /dev/null @@ -1,47 +0,0 @@ -let angApp = require(__dirname+'/../init') - -angApp.controller('DefaultController', function($scope, $timeout, $http, AppInfo) { - - $scope.appInfo = AppInfo - - let getUpdateInfo = () => { - $http({ - url: $scope.appInfo.APP_RELEASES_CHECK_URL, - method: 'GET' - }).then((response) => { - let releaseTag = response.data[0].tag_name - let releaseVersion = parseInt(releaseTag.replace(/v|\./g, '')) - let appVersion = parseInt($scope.appInfo.APP_VERSION.replace(/v|\./g, '')) - if (appVersion < releaseVersion ) { - $scope.checkingForUpdate = 'updateAvailable' - } - else { - $scope.checkingForUpdate = 'latest' - } - }).catch((error) => { - $scope.checkingForUpdate = 'checkErr' - }) - } - - let checkForUpdate = (timeout = 5000) => { - $scope.checkingForUpdate = 'inProgress' - $timeout(() => { - getUpdateInfo() - }, timeout) - } - - let checkForUpdateDelayed = (timeout = 5000) => { - $timeout(() => { - checkForUpdate() - }, timeout) - } - - checkForUpdateDelayed() - - $scope.checkRetry = ($event) => { - $event.preventDefault() - checkForUpdate() - } - - -}) \ No newline at end of file diff --git a/app/controllers/footer-controller.js b/app/controllers/footer-controller.js deleted file mode 100644 index 6747c82..0000000 --- a/app/controllers/footer-controller.js +++ /dev/null @@ -1,7 +0,0 @@ -let angApp = require(__dirname+'/../init') - -angApp.controller('FooterController', function($scope, AppInfo) { - - $scope.appInfo = AppInfo - -}) \ No newline at end of file diff --git a/app/controllers/login-controller.js b/app/controllers/login-controller.js deleted file mode 100644 index c364351..0000000 --- a/app/controllers/login-controller.js +++ /dev/null @@ -1,21 +0,0 @@ -let angApp = require(__dirname+'/../init') - -angApp.controller('LoginController', function($scope, DesktopService, CredentialsService) { - - $scope.help = {} - - $scope.showHelp = (item) => { - $scope.help[item] = typeof $scope.help[item] === 'undefined' ? true : !$scope.help[item]; - } - - $scope.addAccountAndLoginAction = () => { - CredentialsService.addCredentials($scope.credentials.connectionManager, - $scope.credentials.login, - $scope.credentials.password - ) - DesktopService.getCredentialsAndLogin() - $scope.accountForm.$setPristine() - $scope.accountForm.$setUntouched() - $scope.credentials = {} - } -}) \ No newline at end of file diff --git a/app/controllers/settings-controller.js b/app/controllers/settings-controller.js deleted file mode 100644 index be5154f..0000000 --- a/app/controllers/settings-controller.js +++ /dev/null @@ -1,40 +0,0 @@ -let angApp = require(__dirname+'/../init') - -angApp.controller('SettingsController', function ($scope, $rootScope, AppStateService, SettingsService) { - - let formInitialized = false - $scope.settingsChanged = false - $scope.settingsSaved = false - - const settingsSetPristine = () => { - $scope.settingsChanged = false - formInitialized = false - } - - $scope.closeSettings = () => { - $scope.settings = SettingsService.loadAll() - settingsSetPristine() - AppStateService.set(AppStateService.APP_STATE_DEFAULT) - } - - $scope.saveSettings = () => { - $scope.settingsSaved = true - SettingsService.saveAll($scope.settings) - settingsSetPristine() - } - - $scope.restartApp = () => { - $rootScope.$broadcast('app:restart') - } - - $scope.settings = SettingsService.loadAll() - - $scope.$watch("settings", () => { - if (!formInitialized) { - formInitialized = true - } else { - $scope.settingsChanged = true - } - }, true) - -}) \ No newline at end of file diff --git a/app/converse-plugins/desktop-credentials.js b/app/converse-plugins/desktop-credentials.js new file mode 100644 index 0000000..08b0a24 --- /dev/null +++ b/app/converse-plugins/desktop-credentials.js @@ -0,0 +1,24 @@ +const { addCredentials } = require('../credentials.js'); +const { getCredentials, removeCredentials } = require('../credentials.js'); + +converse.plugins.add('converse-desktop-credentials', { + + initialize () { + const { _converse } = this; + const { api } = _converse; + + api.listen.on('afterResourceBinding', () => { + if (_converse.connection.pass) { + addCredentials( + converse.connectionManager, + _converse.bare_jid, + _converse.connection.pass + ); + } + }); + + api.listen.on('logout', () => { + getCredentials().then((result) => removeCredentials(result.login)) + }); + } +}); diff --git a/app/credentials.js b/app/credentials.js new file mode 100644 index 0000000..ce0e9dd --- /dev/null +++ b/app/credentials.js @@ -0,0 +1,49 @@ +/* global require, module */ + +const settings = require('electron-settings'); +const keytar = require('keytar') + +function addCredentials (connectionManager, login, password) { + const xmppService = login.split('@').pop() + settings.setSync('connectionManager', connectionManager) + settings.setSync('login', login) + keytar.setPassword(xmppService, login, password) +} + +function getCredentials () { + const credentials = {} + credentials.login = settings.getSync('login') + return new Promise((resolve) => { + if (credentials.login) { + credentials.connectionManager = settings.getSync('connectionManager') + credentials.xmppService = credentials.login.split('@').pop() + let password = keytar.getPassword(credentials.xmppService, credentials.login) + password.then((result) => { + credentials.password = result + resolve(credentials) + }) + } else { + resolve({}); + } + }); +} + +function removeCredentials (login) { + const xmppService = login.split('@').pop() + const passwordDelete = keytar.deletePassword(xmppService, login) + return new Promise((resolve, reject) => { + passwordDelete.then(() => { + settings.unsetSync('login') + settings.unsetSync('connectionManager') + resolve() + }, (error) => { + reject(error) + }) + }) +} + +module.exports = { + addCredentials, + getCredentials, + removeCredentials +} diff --git a/app/init.js b/app/init.js deleted file mode 100644 index 266e9d5..0000000 --- a/app/init.js +++ /dev/null @@ -1,14 +0,0 @@ -const angular = require('angular') -let angApp = angular.module('app', []) - -angApp.constant('AppInfo', { - APP_NAME: 'Converse Desktop', - APP_VERSION: 'v0.1.0', - APP_HOME: 'https://github.com/conversejs/converse-desktop', - APP_RELEASES_CHECK_URL: 'https://api.github.com/repos/conversejs/converse-desktop/releases', - APP_RELEASES_URL: 'https://github.com/conversejs/converse-desktop/releases', - CONVERSE_VERSION: 'v7.0.4', - ELECTRON_VERSION: '11.2.3' -}); - -module.exports = angApp diff --git a/app/services/app-state-service.js b/app/services/app-state-service.js deleted file mode 100644 index 9c4ba66..0000000 --- a/app/services/app-state-service.js +++ /dev/null @@ -1,22 +0,0 @@ -let angApp = require(__dirname + '/../init') - -angApp.factory('AppStateService', [ '$rootScope', ($rootScope) => { - - let stateService = {} - - stateService.APP_STATE_LOGIN = 'login' - stateService.APP_STATE_DEFAULT = 'default' - stateService.APP_STATE_SETTINGS = 'settings' - stateService.APP_STATE_ABOUT = 'about' - - stateService.set = (state) => { - stateService.previousState = typeof stateService.state !== 'undefined' ? - stateService.state : stateService.APP_STATE_DEFAULT - stateService.state = state - $rootScope.$broadcast('app:state:changed', stateService.state) - } - - stateService.set(stateService.APP_STATE_DEFAULT) - - return stateService -}]) \ No newline at end of file diff --git a/app/services/credentials-service.js b/app/services/credentials-service.js deleted file mode 100644 index cd6831f..0000000 --- a/app/services/credentials-service.js +++ /dev/null @@ -1,54 +0,0 @@ -let angApp = require(__dirname+'/../init') - -angApp.factory('CredentialsService', () => { - - const keytar = require('keytar') - const settings = require('electron-settings') - - let credentialsService = {} - - credentialsService.getCredentials = () => { - let credentials = {} - credentials.login = settings.getSync('login') - let promise = new Promise((resolve, reject) => { - if (credentials.login) { - credentials.connectionManager = settings.getSync('connectionManager') - credentials.xmppService = credentials.login.split('@').pop() - let password = keytar.getPassword(credentials.xmppService, credentials.login) - password.then((result) => { - credentials.password = result - resolve(credentials) - }) - } - else { - reject(Error('No login stored')) - } - }) - return promise - } - - credentialsService.addCredentials = (connectionManager, login, password) => { - let xmppService = login.split('@').pop() - settings.setSync('connectionManager', connectionManager) - settings.setSync('login', login) - keytar.setPassword(xmppService, login, password) - } - - credentialsService.removeCredentials = (login) => { - let xmppService = login.split('@').pop() - passwordDelete = keytar.deletePassword(xmppService, login) - let promise = new Promise((resolve, reject) => { - passwordDelete.then((result) => { - settings.unsetSync('login') - settings.unsetSync('connectionManager') - resolve() - }, (error) => { - reject(error) - }) - }) - return promise - } - - return credentialsService -}) - diff --git a/app/services/desktop-service.js b/app/services/desktop-service.js deleted file mode 100644 index ebaec6a..0000000 --- a/app/services/desktop-service.js +++ /dev/null @@ -1,96 +0,0 @@ -let angApp = require(__dirname + '/../init') - -const desktopPlugin = require(__dirname +'/../../libs/converse.js/converse-desktop/desktop-plugin') - -angApp.factory('DesktopService', ( - $window, $timeout, CredentialsService, SystemService, AppStateService, - SettingsService, XmppHelperService - ) => { - - let desktopService = {} - - desktopService._notifyMessage = () => { - SystemService.playAudio() - SystemService.showEnvelope() - } - - desktopService._hideNotifyMessage = () => { - SystemService.hideEnvelope() - } - - desktopService.logout = () => { - let credentials = CredentialsService.getCredentials() - credentials.then((result) => { - let remove = CredentialsService.removeCredentials(result.login) - console.log('Remove credentials on logout') - remove.then(() => { - AppStateService.set(AppStateService.APP_STATE_LOGIN) - }) - }) - } - - desktopService.initConverse = (connectionManager, login, password) => { - AppStateService.set(AppStateService.APP_STATE_DEFAULT) // Always set to default state before init - desktopPlugin.register(login) - let lang = navigator.language - let allowBookmarks = SettingsService.get('allowBookmarks') - let omemoDefault = SettingsService.get('omemoDefault') - let xmppResource = XmppHelperService.getResourceFromJid(login) - if (!xmppResource) { - xmppResource = '.' + (Math.random().toString(36)+'00000000000000000').slice(2, 7) // Generate 5 char unique str - login = login + '/converseDesktop'+xmppResource - } - let conversejsParams = { - assets_path: './node_modules/converse.js/dist/', - allow_bookmarks: allowBookmarks, - auto_login: true, - auto_reconnect: true, - // debug: true, - i18n: lang, - jid: login, - omemo_default: omemoDefault, - password: password, - play_sounds: false, - priority: 50, - view_mode: 'embedded', - whitelisted_plugins: ['converseDesktop'], - } - if (connectionManager.startsWith('ws')) { - conversejsParams.websocket_url = connectionManager - } else { - conversejsParams.bosh_service_url = connectionManager - } - $timeout(() => { - converse.initialize(conversejsParams) - }, 50) - } - - desktopService.getCredentialsAndLogin = () => { - let credentials = CredentialsService.getCredentials() - credentials.then((result) => { - desktopService.initConverse(result.connectionManager, result.login, result.password) - }, (error) => { - AppStateService.set(AppStateService.APP_STATE_LOGIN) - }) - } - - - desktopService.chatToOpen = null - - $window.document.addEventListener('conversejs-logout', function (e) { - desktopService.logout() - }) - - $window.document.addEventListener('conversejs-unread', function (e) { - let sender = e.detail - desktopService.chatToOpen = sender - desktopService._notifyMessage() - }) - - $window.document.addEventListener('conversejs-no-unread', function (e) { - desktopService._hideNotifyMessage() - }) - - return desktopService - -}) \ No newline at end of file diff --git a/app/services/settings-service.js b/app/services/settings-service.js deleted file mode 100644 index 0d19ac6..0000000 --- a/app/services/settings-service.js +++ /dev/null @@ -1,109 +0,0 @@ -let angApp = require(__dirname+'/../init') - -angApp.factory('SettingsService', () => { - - let settingsService = {} - - const electronSettings = require('electron-settings') - - const settings = { - converseDesktop: { - runMinimized: { - default: false, - title: 'Run minimized', - hint: 'Default: false. Whether run Converse Desktop minimized to tray or not.' - }, - minimizeOnClose: { - default: false, - title: 'Minimize on close', - hint: 'Default: false. Minimize or close Converse Desktop window.' - }, - preserveWindowSize: { - default: true, - title: 'Preserve window size', - hint: 'Default: true, 800x600 otherwise.' - }, - preserveWindowPosition: { - default: true, - title: 'Preserve window position', - hint: 'Default: true, screen center otherwise.' - } - }, - conversejs: { - allowBookmarks: { - default: false, - title: 'Allow server bookmarks', - hint: 'Default: false. Enables/disables chatroom bookmarks functionality.' - }, - omemoDefault: { - default: false, - title: 'Use OMEMO encryption by default', - hint: 'Default: false. Use OMEMO encryption by default when the chat supports it.' - } - } - } - - const iterateSettings = (callback, settingsObj) => { - if(typeof settingsObj === "undefined") { - settingsObj = settings - } - angular.forEach(settingsObj, (value, key) => { - let settingsList = settingsObj[key] - angular.forEach(settingsList, (value, key) => { - let itemDefault = settingsList[key]['default'] - callback(key, itemDefault, settingsList) - }) - }) - } - - // Callback - // TODO: replace with promise? - const saveDefault = (key, value) => { - if (!electronSettings.hasSync(key)) { - electronSettings.setSync(key, value) - } - } - - // Callback - const save = (key, defaultValue, settingsList) => { - let value = settingsList[key]['value'] - electronSettings.setSync(key, value) - } - - // Callback - const loadAll = (key, defaultValue, settingsList) => { - if (!electronSettings.hasSync(key)) { - settingsList[key]['value'] = defaultValue - } - settingsList[key]['value'] = electronSettings.getSync(key) - } - - /** - * SettingsService - */ - settingsService.initDefaults = () => { - iterateSettings(saveDefault) - // Logout for versions with BOSH only - if (electronSettings.hasSync('bosh')) { - electronSettings.unsetSync('bosh') - electronSettings.unsetSync('login') - } - } - - settingsService.get = (key) => { - return electronSettings.getSync(key) - } - - settingsService.loadAll = () => { - let currentSettings = angular.copy(settings) - iterateSettings(loadAll, currentSettings) - return currentSettings - } - - settingsService.saveAll = (currentSettings) => { - iterateSettings(save, currentSettings) - } - - return settingsService -}) - diff --git a/app/services/system-service.js b/app/services/system-service.js deleted file mode 100644 index 98f2af1..0000000 --- a/app/services/system-service.js +++ /dev/null @@ -1,28 +0,0 @@ -let angApp = require(__dirname + '/../init') - -angApp.factory('SystemService', () => { - - const remote = require('electron').remote - - let systemService = {} - - systemService.playAudio = () => { - var audio = new Audio(__dirname + '/../../resources/sounds/graceful.ogg') - audio.play() - } - - systemService.showEnvelope = () => { - remote.require('./main').trayService.showEnvelope() - } - - systemService.hideEnvelope = () => { - remote.require('./main').trayService.hideEnvelope() - } - - systemService.reloadWindow = () => { - remote.getCurrentWindow().reload() - } - - return systemService - -}) \ No newline at end of file diff --git a/app/services/xmpp-helper-service.js b/app/services/xmpp-helper-service.js deleted file mode 100644 index 74408ab..0000000 --- a/app/services/xmpp-helper-service.js +++ /dev/null @@ -1,25 +0,0 @@ -let angApp = require(__dirname + '/../init') - -angApp.factory('XmppHelperService', [() => { - - let xmppHelperService = {} - - /** - * Use function copy from Strophe js lib because converse.js Strophe library - * is under private _api and unavailable before converse.js is initialized. - * This function is used _before_ converse.js is initialized. - * - * Get the resource portion of a JID String. - * @param {string} jid A JID. - * @return {string | null} A String containing the resource. - */ - xmppHelperService.getResourceFromJid = (jid) => { - if (!jid) { return null } - const s = jid.split("/") - if (s.length < 2) { return null } - s.splice(0, 1) - return s.join('/') - } - - return xmppHelperService -}]) \ No newline at end of file diff --git a/app/views/about/page.html b/app/views/about/page.html deleted file mode 100644 index a959743..0000000 --- a/app/views/about/page.html +++ /dev/null @@ -1,35 +0,0 @@ -
-
-
- -
-

About {{appInfo.APP_NAME}} {{appInfo.APP_VERSION}}

-
Jabber/XMPP client based on Converse.js and Electron
-
 
-
- Version of converse.js is {{appInfo.CONVERSE_VERSION}} -
-
- Version of electron is {{appInfo.ELECTRON_VERSION}} -
-
 
-
- Thanks to - all contributors -
-
 
-
-
- -
- -
-
 
- -
-
- - \ No newline at end of file diff --git a/app/views/default/page.html b/app/views/default/page.html deleted file mode 100644 index de6efd7..0000000 --- a/app/views/default/page.html +++ /dev/null @@ -1,24 +0,0 @@ -
-
- -

{{appInfo.APP_NAME}}

-
- {{appInfo.APP_VERSION}} - - checking for update... - - >_< check for update failed retry - - latest version - - Update available - - -
-
-
-
-
-
diff --git a/app/views/login/page.html b/app/views/login/page.html deleted file mode 100644 index e7e0525..0000000 --- a/app/views/login/page.html +++ /dev/null @@ -1,43 +0,0 @@ -
- - - -
- \ No newline at end of file diff --git a/app/views/settings/page.html b/app/views/settings/page.html deleted file mode 100644 index 9595e03..0000000 --- a/app/views/settings/page.html +++ /dev/null @@ -1,28 +0,0 @@ -
-
-

Chimeverse settings

-
- -
{{item.hint}}
-
-

Converse.js settings

-
- -
{{item.hint}}
-
- -
- - -
-
-
- \ No newline at end of file diff --git a/app/views/shared/_footer.html b/app/views/shared/_footer.html deleted file mode 100644 index 950c838..0000000 --- a/app/views/shared/_footer.html +++ /dev/null @@ -1,4 +0,0 @@ - \ No newline at end of file diff --git a/index.html b/index.html index 931266a..44f8030 100644 --- a/index.html +++ b/index.html @@ -7,28 +7,17 @@ - - - -
-
-
- -
-
- -
-
- -
-
- + +
+
+
+ @@ -37,27 +26,15 @@ delete window.require - + - - \ No newline at end of file + diff --git a/libs/converse.js/converse-desktop/desktop-plugin.js b/libs/converse.js/converse-desktop/desktop-plugin.js deleted file mode 100644 index 931f59a..0000000 --- a/libs/converse.js/converse-desktop/desktop-plugin.js +++ /dev/null @@ -1,67 +0,0 @@ -let desktopPlugin = {} - -desktopPlugin.register = (login) => { - converse.plugins.add('converseDesktop', { - initialize: (event) => { - let _converse = event.properties._converse - let Strophe = converse.env.Strophe - - /** - * Check if message stanza has some body payload - * @param {*} stanzaNodes - */ - let isBodyMessage = (stanzaNodes) => { - let result = false - Object.keys(stanzaNodes).some((key) => { - if (stanzaNodes[key].nodeName == 'body') { - result = true - } - }) - return result - } - - Promise.all([ - _converse.api.waitUntil('rosterContactsFetched'), - _converse.api.waitUntil('chatBoxesFetched') - ]).then(() => { - _converse.api.listen.on('logout', () => { - let event = new CustomEvent('conversejs-logout') - document.dispatchEvent(event) - }) - _converse.api.listen.on('message', (data) => { - // Display notifications only for "payloaded" messages - if (isBodyMessage(data.stanza.childNodes)) { - let sender = data.stanza.attributes.from.nodeValue - let senderJid = Strophe.getBareJidFromJid(sender) - let loginJid = Strophe.getBareJidFromJid(login) - if (senderJid != loginJid) { - console.log(senderJid) - let event = new CustomEvent('conversejs-unread', {detail: senderJid}) - document.dispatchEvent(event) - } - } - }) - _converse.api.listen.on('chatBoxFocused', () => { - let event = new CustomEvent('conversejs-no-unread') - document.dispatchEvent(event) - //chimeverseService._hideNotifyMessage() - }) - window.document.addEventListener('converse-force-logout', function (e) { - console.log('Get converse-force-logout event') - console.log('Logout form plugin') - _converse.api.user.logout() - //chimeverseService.logout() - }) - window.document.addEventListener('conversejs-open-chat', function (e) { - let chatToOpen = e.detail - console.log('Get open-unread-chat event: '+chatToOpen) - if (chatToOpen !== null) { - _converse.api.chats.open(chatToOpen) - } - }) - }) - } - }) -} - -module.exports = desktopPlugin \ No newline at end of file diff --git a/main.js b/main.js index 6335b91..cb34722 100644 --- a/main.js +++ b/main.js @@ -8,7 +8,7 @@ let mainWindow // Require other app modules const trayService = require(__dirname+'/modules/tray-service') const menuService = require(__dirname+'/modules/menu-service') -const settingsService = require(__dirname+'/modules/settings-service') +// const settingsService = require(__dirname+'/modules/settings-service') const isMac = process.platform === 'darwin' const isWin = process.platform === 'win32' @@ -23,41 +23,19 @@ function initApp() { function createWindow () { // Main window options - let mainWindowOptions = { - width: 800, - height: 600, - minWidth: 780, - minHeight: 560, + const mainWindowOptions = { + zoomToPageWidth: true, webPreferences: { nodeIntegration: true, contextIsolation: false, enableRemoteModule: true - } - } - - // Load app settings - let runMinimized = settingsService.get('runMinimized') - if (runMinimized) { - mainWindowOptions.show = !runMinimized - } - let preserveWindowSize = settingsService.get('preserveWindowSize') - if (preserveWindowSize) { - let width = settingsService.get('windowWidth') - let height = settingsService.get('windowHeight') - if (width) mainWindowOptions.width = width - if (height) mainWindowOptions.height = height - } - - let preserveWindowPosition = settingsService.get('preserveWindowPosition') - if (preserveWindowPosition) { - let windowX = settingsService.get('windowX') - let windowY = settingsService.get('windowY') - if (windowX) mainWindowOptions.x = windowX - if (windowY) mainWindowOptions.y = windowY + }, + icon: './resources/images/logo.png', } // Create the browser window. mainWindow = new BrowserWindow(mainWindowOptions) + mainWindow.maximize(); // and load the index.html of the app. mainWindow.loadFile('index.html') @@ -72,35 +50,15 @@ function createWindow () { // mainWindow.webContents.openDevTools() // Before close - let minimizeOnClose = settingsService.get('minimizeOnClose') + // const minimizeOnClose = settingsService.get('minimizeOnClose'); + const minimizeOnClose = false; // XXX: this doesn't seem to work if (minimizeOnClose) { mainWindow.on('close', (e) => { if (!app.isQuitting) { e.preventDefault() mainWindow.hide() } - }) - } - - // Save window size - if (preserveWindowSize) { - mainWindow.on('resize', (e) => { - let newSize = mainWindow.getSize() - let width = newSize[0] - let height = newSize[1] - settingsService.set('windowWidth', width) - settingsService.set('windowHeight', height) - }) - } - - // Save window position - if (preserveWindowPosition !== 'undefined') { - mainWindow.on('move', (e) => { - let newPosition = mainWindow.getPosition() - let windowX = newPosition[0] - let windowY = newPosition[1] - settingsService.set('windowX', windowX) - settingsService.set('windowY', windowY) + return false; }) } @@ -115,7 +73,7 @@ function createWindow () { } // Handle restart - ipcMain.on('app-restart', (evt, arg) => { + ipcMain.on('app-restart', () => { app.isQuitting = true app.relaunch() app.exit() diff --git a/modules/menu-service.js b/modules/menu-service.js index 55f0280..d8d1d09 100644 --- a/modules/menu-service.js +++ b/modules/menu-service.js @@ -1,61 +1,24 @@ /** * Module for Menu functions. */ - const {app, Menu, BrowserWindow} = require('electron') -let menuService = {} +const menuService = {} menuService.createMenu = () => { - - const isMac = process.platform === 'darwin' - - const about = { - label: 'About Converse Desktop', - click: () => { - // @see https://github.com/electron/electron/issues/16558#issuecomment-484460276 - // let activeWindow = BrowserWindow.getFocusedWindow() - let activeWindow = BrowserWindow.getAllWindows()[0] - activeWindow.show() - activeWindow.webContents.send('about-page-event') - } - } - const application = { label: 'Converse Desktop', submenu: [ - ... isMac ? [about] : [], { label: 'Reconnect', accelerator: 'CmdOrCtrl+R', click: () => { - let activeWindow = BrowserWindow.getAllWindows()[0] + const activeWindow = BrowserWindow.getAllWindows()[0] activeWindow.show() activeWindow.reload() } }, - { - label: 'Force logout', - accelerator: 'CmdOrCtrl+D', - click: () => { - let activeWindow = BrowserWindow.getAllWindows()[0] - activeWindow.show() - activeWindow.webContents.send('force-logout-event') - } - }, - { - type: 'separator', - }, - { - label: 'Preferences', - accelerator: 'CmdOrCtrl+,', - click: () => { - let activeWindow = BrowserWindow.getAllWindows()[0] - activeWindow.show() - activeWindow.webContents.send('preferences-event') - } - }, { type: 'separator', }, @@ -110,12 +73,11 @@ menuService.createMenu = () => { const help = { label: 'Help', submenu: [ - ... !isMac ? [about] : [], { label: 'Debug info', accelerator: 'F12', click: () => { - let activeWindow = BrowserWindow.getAllWindows()[0] + const activeWindow = BrowserWindow.getAllWindows()[0] activeWindow.webContents.openDevTools() } } diff --git a/modules/settings-service.js b/modules/settings-service.js index 1f93bb0..fd503b8 100644 --- a/modules/settings-service.js +++ b/modules/settings-service.js @@ -7,7 +7,7 @@ const electronSettings = require('electron-settings') let settingsService = {} settingsService.get = (itemKey) => { - settingValue = electronSettings.getSync(itemKey) + const settingValue = electronSettings.getSync(itemKey) if (typeof settingValue === 'undefined' || settingValue === null) { return false } @@ -18,4 +18,4 @@ settingsService.set = (itemKey, settingValue) => { electronSettings.setSync(itemKey, settingValue) } -module.exports = settingsService \ No newline at end of file +module.exports = settingsService diff --git a/modules/tray-service.js b/modules/tray-service.js index 4c7aabd..e6facae 100644 --- a/modules/tray-service.js +++ b/modules/tray-service.js @@ -9,17 +9,15 @@ const path = require('path') let trayServiceWindow = null let tray = null -let trayService = {} +const trayService = {} -let getTrayServiceIcon = (iconName = 'icon') => { +const getTrayServiceIcon = (iconName = 'icon') => { let iconImage = '' if (process.platform === 'darwin') { iconImage = iconName+'Template' - } - else if (process.platform === 'win32') { + } else if (process.platform === 'win32') { iconImage = iconName+'-16x16' - } - else { + } else { iconImage = iconName+'-48x48' } return path.join(__dirname, '/../resources/images/' + iconImage + '.png') @@ -27,12 +25,12 @@ let getTrayServiceIcon = (iconName = 'icon') => { trayService.initTray = (window) => { trayServiceWindow = window - let iconPath = getTrayServiceIcon() + const iconPath = getTrayServiceIcon() tray = new Tray(iconPath) tray.setToolTip('Converse Desktop') tray.on('click', function() { // Sent open-related-chat event only on click - let activeWindow = BrowserWindow.getAllWindows()[0] + const activeWindow = BrowserWindow.getAllWindows()[0] activeWindow.webContents.send('open-unread-chat') trayService.hideEnvelope() trayServiceWindow.show() @@ -40,12 +38,12 @@ trayService.initTray = (window) => { } trayService.showEnvelope = () => { - let iconPath = getTrayServiceIcon('envelope') + const iconPath = getTrayServiceIcon('envelope') tray.setImage(iconPath) } trayService.hideEnvelope = () => { - let iconPath = getTrayServiceIcon() + const iconPath = getTrayServiceIcon() tray.setImage(iconPath) } diff --git a/package.json b/package.json index 3d716cd..edfeb56 100644 --- a/package.json +++ b/package.json @@ -20,16 +20,16 @@ "Electron", "OMEMO" ], - "author": "Nick Denry ", "license": "MPL-2.0", "devDependencies": { "electron": "11.2.3", "electron-builder": "^22.9.1", - "electron-packager": "^15.2.0" + "electron-packager": "^15.2.0", + "electron-rebuild": "^3.2.5", + "eslint": "^8.4.1" }, "dependencies": { - "angular": "^1.7.9", - "converse.js": "7.0.4", + "converse.js": "conversejs/converse.js#ba6da97416b912a35060a4c5667bc77f76852780", "electron-settings": "^4.0.2", "github-buttons": "^2.8.0", "keytar": "^7.3.0", @@ -59,7 +59,7 @@ }, "linux": { "artifactName": "converse_desktop-${version}_${arch}.${ext}", - "icon": "resources/images/logo@2x.png", + "icon": "resources/images/logo.png", "target": [ "deb", "tar.gz" diff --git a/renderer.js b/renderer.js deleted file mode 100644 index ccb308d..0000000 --- a/renderer.js +++ /dev/null @@ -1,64 +0,0 @@ -// This file is required by the index.html file and will -// be executed in the renderer process for that window. -// All of the Node.js APIs are available in this process. - -var angApp = require('./app/init') - -require('./app/services/credentials-service') -require('./app/services/settings-service') -require('./app/services/system-service') -require('./app/services/app-state-service') -require('./app/services/xmpp-helper-service') -require('./app/services/desktop-service') -require('./app/controllers/settings-controller') -require('./app/controllers/login-controller') -require('./app/controllers/default-controller') -require('./app/controllers/about-controller') -require('./app/controllers/footer-controller') - -angApp.controller('AppController', function ($scope, $timeout, DesktopService, SettingsService, AppStateService) { - - const { ipcRenderer } = require('electron') - - // Menu force logout event - ipcRenderer.on('force-logout-event', () => { - DesktopService.logout() - let event = new CustomEvent("converse-force-logout") // Dispatch to the plugin - document.dispatchEvent(event) - }) - - // Menu settings event - ipcRenderer.on('preferences-event', () => { - AppStateService.set(AppStateService.APP_STATE_SETTINGS) - }) - - // Menu about event - ipcRenderer.on('about-page-event', () => { - AppStateService.set(AppStateService.APP_STATE_ABOUT) - }) - - // Menu about event - ipcRenderer.on('open-unread-chat', () => { - let event = new CustomEvent('conversejs-open-chat', {detail: DesktopService.chatToOpen}) - document.dispatchEvent(event) - }) - - AppStateService.set(AppStateService.APP_STATE_DEFAULT) - - $scope.$on('app:state:changed', (event, data) => { - // @see https://docs.angularjs.org/error/$rootScope/inprog - $timeout(() => { - $scope.state = data - console.log('Switch to the "' + $scope.state +'" state') - }, 0) - }) - - $scope.$on('app:restart', (event, data) => { - ipcRenderer.send('app-restart') - }) - - SettingsService.initDefaults() - - DesktopService.getCredentialsAndLogin() - -}) diff --git a/resources/css/app.css b/resources/css/app.css index 261a781..931582e 100644 --- a/resources/css/app.css +++ b/resources/css/app.css @@ -20,16 +20,6 @@ a { color: #777; } -.main-background { - background: #f8f8f8; - height: 100%; - left: 0px; - position: fixed; - top: 0px; - width: 100%; - z-index: -1; -} - .conversejs-adoption { background: transparent; pointer-events: none; diff --git a/resources/css/page-about.css b/resources/css/page-about.css deleted file mode 100644 index 0e69140..0000000 --- a/resources/css/page-about.css +++ /dev/null @@ -1,39 +0,0 @@ -.page-about { - color: #777; - left: 50%; - position: absolute; - top: 47%; - transform: translate(-50%, -50%); -} - -.about-card { - background: #fff; - box-shadow: 0px 0px 5px rgba(0, 0, 0, 0.1); - margin-top: 30px; - min-width: 400px; - padding: 30px; -} - -.about-card__content { - display: flex; -} - -.about__action { - text-align: center; -} - -.about__close-button { - border: 0px solid #ddd; - background: rgb(5,93,228); - background: linear-gradient(0deg, rgba(5,93,228,1) 0%, rgba(76,145,255,1) 100%); - color: #fff; - cursor: pointer; - display: inline-block; - font-size: 18px; - outline: none; - padding: 10px 30px; - } - - .about__copyright { - text-align: center; - } \ No newline at end of file diff --git a/resources/css/page-login.css b/resources/css/page-login.css deleted file mode 100644 index 7bf56bb..0000000 --- a/resources/css/page-login.css +++ /dev/null @@ -1,100 +0,0 @@ -.login-form { - color: #777; - left: 50%; - position: absolute; - top: 47%; - transform: translate(-50%, -50%); -} - -.login-form__title { - font-size: 28px; - text-align: center; - font-weight: normal; - margin: 0px 0px 15px 0px; -} - -.login-form__description { - text-align: center; -} - -.login-form__card { - background: #fff; - box-shadow: 0px 0px 5px rgba(0, 0, 0, 0.1); - margin-top: 30px; - padding: 30px; -} - -.login-form__wrapper { - margin-bottom: 15px;; -} - -.login-form__input-group { - border: 1px solid #ddd; - border-radius: 0px; - padding: 0px 0px 0px 30px; - position: relative; -} - -.login-form__input-group:active, -.login-form__input-group:focus, -.login-form__input-group:hover { - border: 1px solid #aaa; -} - -.login-form__input-group .group-prepend { - position: absolute; - left: 15px; - top: 12px; -} - -.login-form__input-group .group-append--backgrounded { - background: #f2f2f2; - cursor: pointer; - padding: 12px; - position: absolute; - right: 0px; - top: 0 px; -} - -.login-form__input { - border: 0px; - color: #222; - font-size: 18px; - outline: none; - padding: 10px; - width: 290px; -} - -.login-form__input::placeholder { /* Most modern browsers support this now. */ - color: #ccc; - } - - .login-form__input--with-append { - width: 260px; - } - - .login-form__input-help { - font-size: 13px; - margin: 4px; - } - - .login-form__button { - border: 1px solid #ddd; - background: rgb(5,93,228); - background: linear-gradient(0deg, rgba(5,93,228,1) 0%, rgba(76,145,255,1) 100%); - color: #fff; - display: inline-block; - font-size: 18px; - outline: none; - padding: 10px 30px; - width: 100%; - } - - .login-form__button:disabled { - background: #eee; - } - - .login-form__credentials-message { - font-size: 14px; - text-align: center; - } \ No newline at end of file diff --git a/resources/images/envelope-16x16.png b/resources/images/envelope-16x16.png deleted file mode 100644 index 95ef4acc5ba41733be5f061dcbe1d7f534c22418..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 488 zcmVP)L}000McNliru>|f$z^lMB^zB%N#P&2*e@UkanbsE35UgftP~cO*J?@^w$jGZ zLP5a;U&uxaUtopYOqxW3T#`OyV5(srehctKLlgZ?$-|v4b7K?Yj?^}jp{Hs(de>*9IdeXG^FSglrHUTV;jBMpbc)4^b1 z=JWZGhy;KT5$n3n00e-P%jMZ-vx$vHBU40ZpI|+*YS9K+iW&3-ns!1L9f?~ e1Mok{FFXPsdz`2%yuh{q0000L}000McNliru*f8I zlCRA95deM@1OW(w0F6ddP^;A?Q53P04j#vG#Bp3}CXG;`UzBZy!nkA#}rF zFqFExyR*~N)B2sAotEL@;j~__FPqF|IF6%}dgAeTo6qMnS1J{ZVVH!?X4{g`ugQK3+jCnhFVX`0UOssFDE zz>n%7y7OqSxz-Ukkii0ssI& zlBCEm%u086_jWWIl?MU=lOPBU?FUg5F_vYGi;Ii$Q>RXCU%Pf~MJknwNA}8b9PRSu z%c*nc&hg93%WXcN&y>w(TN)93Hk)nn`Fy5$JWloY_GX<ur0^A_V|p-?7%$*V{%$ zMp73pTu8@aF`3`*H|6tryq<+oQcv_e&$s$~KC>u_*zoXh+HSXR?r+3qW@dDOK)__T z+xbW&q67dK9UVs z8+N-r`-RPJjgOD7Nu^R@c6L@Rl}g32v9UF))w;Dm#%{M~FI~Enl1L=r^ZCplKYpZY zQKA+F#5Zr=sORVBH55gauU@^H)ai7E1C5BAOs2fs?Ou&Wqa9YOHK$UkN(T~#ojG$R zr`2l9!C=tvEK5JE6BGoyR;>Xk+=mzO*q&#F$RJ8ZLsDV56d z#fuj?l}c4UY>ZZ`EqcA)ghHVxzkdB%{rvfJ{c%o!RTC(cN_Z#~(k(13D9vW`uGi~L zbaZr-8{uLynXKaVdJ|n;UAyn!zgL7pAziUptg5-IDIt{4=Ot59Q=O?)O4{4o%MT0; z@Dhmx)#Kj>l^{t{93LNFpPQRgy?ggg#c>?za=F$himKGLo#r@>ynp|`ndf=w`Sa&_ zhr_|`1>bNG1c3$z2f4n!KAz`!a&mIAE0f8P$1Wj+5bPc6s6ZfKESJmp;NalKz`#KI zBu-@g{rww5Lqi*tN(EzC))Ylc6L^~xVWgGD5~OgI+I$hw$P}R zyBaZUHrr-rXJ_%zqeq6vj~{oQJ$rU@b90lzFzm1YB!FSq&2&2bYdW1)Vi<-9g4hX% z!>s_&`YrB-2!hxV1VOQ|u&{p~zj>4bzXL!Q0Q`gyGIAWJ{MHv+dzV`Y0Qdy}{%GDu jHqk^AO*GL&6Ai-O#zL9dA4Lvh00000NkvXXu0mjfyC?NI diff --git a/resources/images/envelopeTemplate.png b/resources/images/envelopeTemplate.png deleted file mode 100644 index 76b6287e3f5b101c67560ad09ea66111af02014c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 362 zcmeAS@N?(olHy`uVBq!ia0vp^Vj#@H1|*Mc$*~4fY)RhkE)4%caKYZ?lYt_f1s;*b zKph)En6WlB@)uB$y~NYkmHi2;3?GNC$hLeDpwM?u7sn8b({HaB>N6Kg9RKLQx@={F z(FQNC-IF8Elr65{O?r`bQT$-ayu>Xzeut=8h$u(&rLNc*8^)S;tndWGcg5wsU4G; z>JI8``!G%Tx3A0%dGw;a5UZGX}*%W3J7)_4GDlx_90?Qt!8(y6-vXp7;Bnp67ecLxBPX3KS?% z;6K3pRwCbkh=!$-``_^QDKeqlKd zVQI$q6|L1Kt$g)!Jc!?)a10A_*mCo6G;)^l7%!6ds+I5wY{M|d!*&s?WE*6?-ZLAX1f8?hcY64LjvXPPsS+IC|gIbe6O z0dvBiONDzf_RTn-$lXI6i9ImN0k>O72S)q@w_E$BBnER_j-ItL$%dxsuaSF?#W4_LF_?c=;wBrKC!ao=!o1?+><7ce?ur)`_VEkt+a2%K`cW#~TBzE9s zLiWNW)vE?8GPA^+JZFg^JgURAHgA$?ZD=O4_9X diff --git a/resources/images/icon-16x16.png b/resources/images/icon-16x16.png index 272cd853118e685427d371bb9d068790f7e79dd0..a5d3430e6cb690ab0335469e06ca3c5a779df52e 100644 GIT binary patch literal 5294 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 N2qwdyUg{B<_+N3o7UKW_ delta 688 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 JPDHLkV1fo8SIqzb diff --git a/resources/images/iconTemplate.png b/resources/images/iconTemplate.png deleted file mode 100644 index c0ed3c6dd108256ea52b566580772ea9146b77b5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 399 zcmV;A0dW3_P)|w<4>|#tIorYj000McNliruhwh##_At4eq5uaeA@s{`k5*>wzo=Pc#3K0#h z&}bB*@scQ3bGMPT`^;+hbTjv#Gv~Q8_YMt(nlke3m_`o0SVkVV{|T{;5?=6t&$!%A90+>o}pL`XvPShu$%E@t<|6@+y!`w$%N_5<36dNYfsZ28d{f!=>3p1FAF}}tEPS9P?NY6z(zT)i{VzVvb tCTbt`E}>!=Tw&l>tp{sZZTy8Q>Id%=I+wj0d6xhH002ovPDHLkV1i-3rh@|w<4>|#tIorYj000McNliru^3ApRD_o(>ZPkGB?F_=^s)|Y4&#{_&pyW<&VuuB&iTHz*Z1eY z*Z!}M9(w4ZheL_O(1(7^#;pAQB=%zqmi%9Yo{Cl2fN$_KhVeYci_hjmEp{PpY7*7z zGmN=YC7)5TqHqSD!JYX&hA;6czQA_u!b}{UYX&fcukv5la8^V$D)%w2!|6CO*UZKu zT#EJhuDJeNEbEro!|`;n_f85#qmaZ6(fm#nHaHrr6b)Wyi6#BJUdf8s33gq(iA{sxnPPm%527gEJ&}6&4)ljn` zvqA%-v7M{%VnxBn<0x!u<-wC-PBPiec&#bc?7@c#lNVPwfF2F++FJ2vE>6R}*jWf4 z!=LfuyQh=Qnlj+#@U5!tzcUim7iO{=Da7xK-EG41X`G_Vlc@LN{BER;EAT`5dw)~w zJe5IaR*MH%k>TkVoZZb3dJG=Q#Pvtor6GK0qW^*k9A;^3vTK4*jB6||e!C=be`|)L zhVYSW;Hz7Y zI`_~X?BE%$TIF;`C#Zmp2p#R?2GY=Zyu&qz)_0C7goE2+a#4y=+#o!M>a}0Z_%F1k z2L$KP`ZqCzze4}55&BG17vWC oYwSYvMDY`MJY_iLDebNwN!k{l7Lv+%FIR$oB5BP0u~(zoZy0}qHvj+t literal 6538 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 diff --git a/resources/images/logo.png b/resources/images/logo.png index df795479b092a73e0d2809eeb3ff45490c6a2977..f85f0087acbf735ccf0a1f34342c6fc749f13bb5 100644 GIT binary patch literal 7321 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 literal 8038 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