From 7d594294594074aab4a07546725906fbdd54ceb1 Mon Sep 17 00:00:00 2001 From: Ira Hopkinson Date: Wed, 30 May 2018 21:33:16 +1200 Subject: [PATCH] Refactor lexicon app to Angular component --- package-lock.json | 16 +- .../core/offline/editor-data.service.ts | 7 +- .../lexicon/editor/editor.module.ts | 10 +- .../lexicon/lexicon-app.component.html | 8 + .../lexicon/lexicon-app.component.ts | 384 ++++++++---------- .../lexicon/lexicon-app.module.ts | 48 ++- .../languageforge/lexicon/lexicon.html | 10 +- 7 files changed, 233 insertions(+), 250 deletions(-) create mode 100644 src/angular-app/languageforge/lexicon/lexicon-app.component.html diff --git a/package-lock.json b/package-lock.json index 27c62def3a..13d325b054 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9821,17 +9821,17 @@ "integrity": "sha512-BRdNm8hbWzFzWHERTrejLqwHDfS4GibPoq5wjTPIoJHoBtKGPg3xAFfxmM+9ztbXelxcf2hwQcaz1PtmFeue8g==", "dev": true, "requires": { - "globule": "1.2.0" + "globule": "1.2.1" } }, "globule": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/globule/-/globule-1.2.0.tgz", - "integrity": "sha1-HcScaCLdnoovoAuiopUAboZkvQk=", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/globule/-/globule-1.2.1.tgz", + "integrity": "sha512-g7QtgWF4uYSL5/dn71WxubOrS7JVGCnFPEnoeChJmBnyR9Mw8nGoEwOgJL/RC2Te0WhbsEUCejfH8SZNJ+adYQ==", "dev": true, "requires": { "glob": "7.1.2", - "lodash": "4.17.5", + "lodash": "4.17.10", "minimatch": "3.0.4" } }, @@ -9876,6 +9876,12 @@ "sshpk": "1.13.1" } }, + "lodash": { + "version": "4.17.10", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.10.tgz", + "integrity": "sha512-UejweD1pDoXu+AD825lWwp4ZGtSwgnpZxb3JDViD7StjQz+Nb/6l093lx4OQ0foGWNRoc19mWy7BzL+UAK2iVg==", + "dev": true + }, "lru-cache": { "version": "4.1.3", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.3.tgz", diff --git a/src/angular-app/bellows/core/offline/editor-data.service.ts b/src/angular-app/bellows/core/offline/editor-data.service.ts index efea426df0..77b2cf8924 100644 --- a/src/angular-app/bellows/core/offline/editor-data.service.ts +++ b/src/angular-app/bellows/core/offline/editor-data.service.ts @@ -34,7 +34,7 @@ class EntryListModifiers { } export class EditorDataService { - readonly browserInstanceId = Math.floor(Math.random() * 1000000); + readonly browserInstanceId: string = Math.floor(Math.random() * 1000000).toString(); entries: any[] = []; visibleEntries: any[] = []; @@ -80,7 +80,7 @@ export class EditorDataService { /** * Called when loading the controller */ - loadEditorData = (lexiconScope?: any): angular.IPromise => { + loadEditorData = (): angular.IPromise => { const deferred = this.$q.defer(); if (this.entries.length === 0) { // first page load if (this.cache.canCache()) { @@ -88,7 +88,6 @@ export class EditorDataService { this.loadDataFromOfflineCache().then((projectObj: any) => { if (projectObj.isComplete) { this.showInitialEntries().then(() => { - lexiconScope.finishedLoading = true; this.notice.cancelLoading(); this.refreshEditorData(projectObj.timestamp).then((result: any) => { deferred.resolve(result); @@ -256,7 +255,7 @@ export class EditorDataService { UtilityService.arrayCopyRetainingReferences(entriesSorted, this.entries); const filteredEntriesSorted = this.sortList(config, this.filteredEntries); UtilityService.arrayCopyRetainingReferences(filteredEntriesSorted, this.filteredEntries); - const visibleEntriesSorted = this.sortList(config, this.visibleEntries); + this.sortList(config, this.visibleEntries); if (shouldResetVisibleEntriesList) { // TODO: Magic number "50" below should become a constant somewhere UtilityService.arrayCopyRetainingReferences(filteredEntriesSorted.slice(0, 50), this.visibleEntries); diff --git a/src/angular-app/languageforge/lexicon/editor/editor.module.ts b/src/angular-app/languageforge/lexicon/editor/editor.module.ts index 94475392bf..ae4cfc71a0 100644 --- a/src/angular-app/languageforge/lexicon/editor/editor.module.ts +++ b/src/angular-app/languageforge/lexicon/editor/editor.module.ts @@ -31,11 +31,11 @@ export const LexiconEditorModule = angular abstract: true, url: '/editor', template: ` - ` + ` }) .state('editor.list', { url: '/list', diff --git a/src/angular-app/languageforge/lexicon/lexicon-app.component.html b/src/angular-app/languageforge/lexicon/lexicon-app.component.html new file mode 100644 index 0000000000..b29090c1ab --- /dev/null +++ b/src/angular-app/languageforge/lexicon/lexicon-app.component.html @@ -0,0 +1,8 @@ +
+
+
+
+ + +
+
diff --git a/src/angular-app/languageforge/lexicon/lexicon-app.component.ts b/src/angular-app/languageforge/lexicon/lexicon-app.component.ts index 06039f6823..b708cdff17 100644 --- a/src/angular-app/languageforge/lexicon/lexicon-app.component.ts +++ b/src/angular-app/languageforge/lexicon/lexicon-app.component.ts @@ -1,252 +1,188 @@ import * as angular from 'angular'; -import uiRouter from 'angular-ui-router'; -import {ApiService} from '../../bellows/core/api/api.service'; -import {BreadcrumbModule} from '../../bellows/core/breadcrumbs/breadcrumb.module'; -import {CoreModule} from '../../bellows/core/core.module'; import {InputSystemsService} from '../../bellows/core/input-systems/input-systems.service'; import {NoticeService} from '../../bellows/core/notice/notice.service'; -import {SessionService} from '../../bellows/core/session.service'; +import {InterfaceConfig} from '../../bellows/shared/model/interface-config.model'; import {User} from '../../bellows/shared/model/user.model'; import {LexiconConfigService} from './core/lexicon-config.service'; -import {LexiconCoreModule} from './core/lexicon-core.module'; import {LexiconEditorDataService} from './core/lexicon-editor-data.service'; import {LexiconProjectService} from './core/lexicon-project.service'; -import {LexiconRightsService} from './core/lexicon-rights.service'; -import {LexiconSendReceiveApiService} from './core/lexicon-send-receive-api.service'; +import {LexiconRightsService, Rights} from './core/lexicon-rights.service'; import {LexiconSendReceiveService} from './core/lexicon-send-receive.service'; -import {LexiconEditorModule} from './editor/editor.module'; -import {LexiconSettingsModule} from './settings/settings.module'; import {LexiconConfig} from './shared/model/lexicon-config.model'; import {LexiconProjectSettings} from './shared/model/lexicon-project-settings.model'; import {LexiconProject} from './shared/model/lexicon-project.model'; import {LexOptionList} from './shared/model/option-list.model'; -// Declare app level module which depends on filters, and services -export const LexiconAppComponentModule = angular - .module('lexicon', [ - 'ui.bootstrap', - uiRouter, - 'ngSanitize', - 'palaso.ui.typeahead', - CoreModule, - BreadcrumbModule, - LexiconCoreModule, - LexiconEditorModule, - LexiconSettingsModule - ]) - .config(['$stateProvider', '$urlRouterProvider', - '$compileProvider', '$sanitizeProvider', 'apiServiceProvider', - ($stateProvider: angular.ui.IStateProvider, $urlRouterProvider: angular.ui.IUrlRouterProvider, - $compileProvider: angular.ICompileProvider, $sanitizeProvider: any, apiService: ApiService) => { - - $compileProvider.debugInfoEnabled(apiService.isProduction); - $compileProvider.commentDirectivesEnabled(apiService.isProduction); - - // this is needed to allow style="font-family" on ng-bind-html elements - $sanitizeProvider.addValidAttrs(['style']); - - $urlRouterProvider.otherwise('/editor/list'); - - // State machine from ui.router - $stateProvider - .state('configuration', { - url: '/configuration', - template: '' - }) - .state('importExport', { - url: '/importExport', - template: '' - }) - .state('settings', { - url: '/settings', - template: '' - }) - .state('sync', { - url: '/sync', - template: '' - }) - ; - - }]) - .controller('LexiconCtrl', ['$scope', '$interval', '$location', '$q', - 'silNoticeService', 'sessionService', 'lexConfigService', - 'lexProjectService', 'lexEditorDataService', - 'lexRightsService', 'lexSendReceiveApi', - 'lexSendReceive', - ($scope: any, $interval: angular.IIntervalService, $location: angular.ILocationService, $q: angular.IQService, - notice: NoticeService, sessionService: SessionService, configService: LexiconConfigService, - lexProjectService: LexiconProjectService, editorService: LexiconEditorDataService, - rightsService: LexiconRightsService, sendReceiveApi: LexiconSendReceiveApiService, - sendReceive: LexiconSendReceiveService) => { - - let pristineLanguageCode: string; - - $scope.finishedLoading = false; - editorService.loadEditorData($scope).then(() => { - $scope.finishedLoading = true; - sendReceive.checkInitialState(); +export class LexiconAppController implements angular.IController { + finishedLoading: boolean = false; + config: LexiconConfig; + editorConfig: LexiconConfig; + interfaceConfig: InterfaceConfig; + optionLists: LexOptionList[]; + project: LexiconProject; + rights: Rights; + users: { [userId: string]: User }; + + private online: boolean; + private pristineLanguageCode: string; + + static $inject = ['$scope', '$location', + '$q', 'silNoticeService', + 'lexConfigService', + 'lexProjectService', + 'lexEditorDataService', + 'lexRightsService', + 'lexSendReceive' + ]; + constructor(private readonly $scope: angular.IScope, private readonly $location: angular.ILocationService, + private readonly $q: angular.IQService, private readonly notice: NoticeService, + private readonly configService: LexiconConfigService, + private readonly lexProjectService: LexiconProjectService, + private readonly editorService: LexiconEditorDataService, + private readonly rightsService: LexiconRightsService, + private readonly sendReceive: LexiconSendReceiveService) { } + + $onInit(): void { + this.editorService.loadEditorData().then(() => { + this.finishedLoading = true; + this.sendReceive.checkInitialState(); }); - $q.all([rightsService.getRights(), configService.getEditorConfig()]).then(([rights, editorConfig]) => { - if (rights.canEditProject()) { - lexProjectService.users().then(result => { - if (result.ok) { - const users = {}; - for (const user of (result.data.users as User[])) { - users[user.id] = user; + this.$q.all([this.rightsService.getRights(), this.configService.getEditorConfig()]) + .then(([rights, editorConfig]) => { + if (rights.canEditProject()) { + this.lexProjectService.users().then(result => { + if (result.ok) { + const users = {}; + for (const user of (result.data.users as User[])) { + users[user.id] = user; + } + + this.users = users; } + }); + } - $scope.users = users; + this.editorConfig = editorConfig; + this.project = rights.session.project(); + this.config = rights.session.projectSettings().config; + this.optionLists = rights.session.projectSettings().optionlists; + this.interfaceConfig = rights.session.projectSettings().interfaceConfig; + this.rights = rights; + this.pristineLanguageCode = angular.copy(this.interfaceConfig.userLanguageCode); + this.changeInterfaceLanguage(this.interfaceConfig.userLanguageCode); + + this.$scope.$watch(() => this.interfaceConfig.userLanguageCode, (newVal: string) => { + if (newVal && newVal !== this.pristineLanguageCode) { + const user = { interfaceLanguageCode: '' }; + user.interfaceLanguageCode = newVal; + this.lexProjectService.updateUserProfile(user); + this.changeInterfaceLanguage(newVal); } }); } - - $scope.editorConfig = editorConfig; - $scope.project = rights.session.project(); - $scope.config = rights.session.projectSettings().config; - $scope.optionLists = rights.session.projectSettings().optionlists; - $scope.rights = rights; - $scope.rights.showControlBar = function showControlBar() { - return $scope.rights.canRemoveUsers() || $scope.rights.canCreateUsers() || - $scope.rights.canEditUsers(); - }; - - $scope.currentUserRole = rights.session.projectSettings().currentUserRole; - $scope.interfaceConfig = rights.session.projectSettings().interfaceConfig; - pristineLanguageCode = angular.copy($scope.interfaceConfig.userLanguageCode); - changeInterfaceLanguage($scope.interfaceConfig.userLanguageCode); - - $scope.showSync = function showSync() { - return !$scope.project.isArchived && rights.canEditUsers() && - rights.session.projectSettings().hasSendReceive; - }; - - $scope.gotoDictionary = function gotoDictionary() { - $location.path('/editor/list'); - }; - - $scope.showDictionaryButton = function showDictionaryButton() { - return !($location.path().indexOf('/editor') === 0); - }; - - $scope.onUpdate = function onUpdate( - $event: { - project?: LexiconProject, - config?: LexiconConfig, - optionLists?: LexOptionList[] - } - ): void { - if ($event.project) { - $scope.project = $event.project; - } - - if ($event.config) { - $scope.config = $event.config; - } - - if ($event.optionLists) { - $scope.optionLists = $event.optionLists; - } - - if ($event.config || $event.optionLists) { - configService.getEditorConfig($scope.config, $scope.optionLists).then(configEditor => { - $scope.editorConfig = configEditor; - }); - } - }; - - function changeInterfaceLanguage(code: string): void { - pristineLanguageCode = angular.copy(code); - if (InputSystemsService.isRightToLeft(code)) { - $scope.interfaceConfig.direction = 'rtl'; - $scope.interfaceConfig.pullToSide = 'float-left'; - $scope.interfaceConfig.pullNormal = 'float-right'; - $scope.interfaceConfig.placementToSide = 'right'; - $scope.interfaceConfig.placementNormal = 'left'; - } else { - $scope.interfaceConfig.direction = 'ltr'; - $scope.interfaceConfig.pullToSide = 'float-right'; - $scope.interfaceConfig.pullNormal = 'float-left'; - $scope.interfaceConfig.placementToSide = 'left'; - $scope.interfaceConfig.placementNormal = 'right'; - } - } - - $scope.$watch('interfaceConfig.userLanguageCode', (newVal: string) => { - if (newVal && newVal !== pristineLanguageCode) { - const user = { interfaceLanguageCode: '' }; - user.interfaceLanguageCode = newVal; - - lexProjectService.updateUserProfile(user); - - changeInterfaceLanguage(newVal); - } + ); + + this.setupOffline(); + } + + $onDestroy(): void { + this.sendReceive.cancelAllStatusTimers(); + } + + onUpdate = ( + $event: { + project?: LexiconProject, + config?: LexiconConfig, + optionLists?: LexOptionList[] + } + ): void => { + if ($event.project) { + this.project = $event.project; + } + + if ($event.config) { + this.config = $event.config; + } + + if ($event.optionLists) { + this.optionLists = $event.optionLists; + } + + if ($event.config || $event.optionLists) { + this.configService.getEditorConfig(this.config, this.optionLists).then(configEditor => { + this.editorConfig = configEditor; }); - - $scope.$on('$destroy', sendReceive.cancelAllStatusTimers); - - // setup offline.js options - // see https://github.com/hubspot/offline for all options - // we tell offline.js to NOT store and remake requests while the connection is down - Offline.options.requests = false; - Offline.options.checkOnLoad = true; - Offline.options.checks = { xhr: { url: '/offlineCheck.txt' } }; - - // Set the page's Language Forge title, font size, and nav's background color - function setTitle(text: string, fontSize: string, backgroundColor: string): void { - const title = document.querySelector('nav .mobile-title a') as HTMLElement; - title.textContent = text; - title.style.fontSize = fontSize; - - document.querySelector('nav a.navbar-brand').textContent = text; - (document.querySelector('nav.navbar') as HTMLElement).style.backgroundColor = backgroundColor; + } + } + + private changeInterfaceLanguage(code: string): void { + this.pristineLanguageCode = angular.copy(code); + if (InputSystemsService.isRightToLeft(code)) { + this.interfaceConfig.direction = 'rtl'; + this.interfaceConfig.pullToSide = 'float-left'; + this.interfaceConfig.pullNormal = 'float-right'; + this.interfaceConfig.placementToSide = 'right'; + this.interfaceConfig.placementNormal = 'left'; + } else { + this.interfaceConfig.direction = 'ltr'; + this.interfaceConfig.pullToSide = 'float-right'; + this.interfaceConfig.pullNormal = 'float-left'; + this.interfaceConfig.placementToSide = 'left'; + this.interfaceConfig.placementNormal = 'right'; + } + } + + private setupOffline(): void { + // setup offline.js options + // see https://github.com/hubspot/offline for all options + // we tell offline.js to NOT store and remake requests while the connection is down + Offline.options.requests = false; + Offline.options.checkOnLoad = true; + Offline.options.checks = { xhr: { url: '/offlineCheck.txt' } }; + + // Set the page's Language Forge title, font size, and nav's background color + function setTitle(text: string, fontSize: string, backgroundColor: string): void { + const title = document.querySelector('nav .mobile-title a') as HTMLElement; + title.textContent = text; + title.style.fontSize = fontSize; + + document.querySelector('nav a.navbar-brand').textContent = text; + (document.querySelector('nav.navbar') as HTMLElement).style.backgroundColor = backgroundColor; + } + + let offlineMessageId: string; + Offline.on('up', () => { + setTitle('Language Forge', '', ''); + + if (this.online === false) { + this.notice.removeById(offlineMessageId); + this.notice.push(this.notice.SUCCESS, 'You are back online!'); } - let offlineMessageId: string; - Offline.on('up', () => { - setTitle('Language Forge', '', ''); - - if ($scope.online === false) { - notice.removeById(offlineMessageId); - notice.push(notice.SUCCESS, 'You are back online!'); - } - - $scope.online = true; - $scope.$digest(); - }); + this.online = true; + this.$scope.$digest(); + }); - Offline.on('down', () => { - setTitle('Language Forge Offline', '0.8em', '#555'); - offlineMessageId = notice.push(notice.ERROR, 'You are offline. Some features are not available', null, true, - 5 * 1000); - $scope.online = false; - if (!/^\/editor\//.test($location.path())) { - // redirect to the editor - $location.path('/editor'); - notice.push(notice.SUCCESS, - 'The dictionary editor is available offline. Settings are not.'); - } + Offline.on('down', () => { + setTitle('Language Forge Offline', '0.8em', '#555'); + offlineMessageId = this.notice.push(this.notice.ERROR, 'You are offline. Some features are not available', null, + true, 5 * 1000); + this.online = false; + if (!/^\/editor\//.test(this.$location.path())) { + // redirect to the editor + this.$location.path('/editor'); + this.notice.push(this.notice.SUCCESS, 'The dictionary editor is available offline. Settings are not.'); + } - $scope.$digest(); - }); - }); - }]) - .controller('BreadcrumbCtrl', ['$scope', '$rootScope', 'breadcrumbService', - ($scope, $rootScope, breadcrumbService) => { - $scope.idmap = breadcrumbService.idmap; - $rootScope.$on('$routeChangeSuccess', () => { - $scope.breadcrumbs = breadcrumbService.read(); + this.$scope.$digest(); }); + } + +} - $scope.$watch('idmap', () => { - $scope.breadcrumbs = breadcrumbService.read(); - }, true); - }]) - .name; +export const LexiconAppComponent: angular.IComponentOptions = { + controller: LexiconAppController, + templateUrl: '/angular-app/languageforge/lexicon/lexicon-app.component.html' +}; diff --git a/src/angular-app/languageforge/lexicon/lexicon-app.module.ts b/src/angular-app/languageforge/lexicon/lexicon-app.module.ts index ed1f82d219..8a44c0fe2a 100644 --- a/src/angular-app/languageforge/lexicon/lexicon-app.module.ts +++ b/src/angular-app/languageforge/lexicon/lexicon-app.module.ts @@ -2,25 +2,67 @@ import * as angular from 'angular'; import 'angular-sanitize'; import uiRouter from 'angular-ui-router'; +import {ApiService} from '../../bellows/core/api/api.service'; import {BreadcrumbModule} from '../../bellows/core/breadcrumbs/breadcrumb.module'; import {CoreModule} from '../../bellows/core/core.module'; import {LexiconCoreModule} from './core/lexicon-core.module'; import {LexiconEditorModule} from './editor/editor.module'; -import {LexiconAppComponentModule} from './lexicon-app.component'; +import {LexiconAppComponent} from './lexicon-app.component'; import './new-project/lexicon-new-project.module'; import {LexiconSettingsModule} from './settings/settings.module'; export const LexiconAppModule = angular - .module('lexiconModule', [ + .module('lexicon', [ 'ui.bootstrap', uiRouter, 'ngSanitize', 'palaso.ui.typeahead', CoreModule, BreadcrumbModule, - LexiconAppComponentModule, LexiconCoreModule, LexiconEditorModule, LexiconSettingsModule ]) + .component('lexiconApp', LexiconAppComponent) + .config(['$stateProvider', '$urlRouterProvider', + '$compileProvider', '$sanitizeProvider', 'apiServiceProvider', + ($stateProvider: angular.ui.IStateProvider, $urlRouterProvider: angular.ui.IUrlRouterProvider, + $compileProvider: angular.ICompileProvider, $sanitizeProvider: any, apiService: ApiService) => { + + $compileProvider.debugInfoEnabled(apiService.isProduction); + $compileProvider.commentDirectivesEnabled(apiService.isProduction); + + // this is needed to allow style="font-family" on ng-bind-html elements + $sanitizeProvider.addValidAttrs(['style']); + + $urlRouterProvider.otherwise('/editor/list'); + + // State machine from ui.router + $stateProvider + .state('configuration', { + url: '/configuration', + template: `` + }) + .state('importExport', { + url: '/importExport', + template: `` + }) + .state('settings', { + url: '/settings', + template: `` + }) + .state('sync', { + url: '/sync', + template: `` + }) + ; + + } + ]) .name; diff --git a/src/angular-app/languageforge/lexicon/lexicon.html b/src/angular-app/languageforge/lexicon/lexicon.html index 889ba806ed..631d05c957 100644 --- a/src/angular-app/languageforge/lexicon/lexicon.html +++ b/src/angular-app/languageforge/lexicon/lexicon.html @@ -1,11 +1,3 @@ {% verbatim %} -
-
-
-
- - -
-
+ {% endverbatim %}