From 529bb08bf9f39a963442a4e701e5c32d2c4c9efd Mon Sep 17 00:00:00 2001 From: Arman Ozak Date: Thu, 28 May 2020 20:52:33 +0300 Subject: [PATCH 01/22] feat: add getLocaleDirection to localization utils --- .../src/lib/tests/localization-utils.spec.ts | 44 +++++++++++++++++++ .../core/src/lib/utils/localization-utils.ts | 9 ++++ 2 files changed, 53 insertions(+) diff --git a/npm/ng-packs/packages/core/src/lib/tests/localization-utils.spec.ts b/npm/ng-packs/packages/core/src/lib/tests/localization-utils.spec.ts index f5319edc2d..1409c0da21 100644 --- a/npm/ng-packs/packages/core/src/lib/tests/localization-utils.spec.ts +++ b/npm/ng-packs/packages/core/src/lib/tests/localization-utils.spec.ts @@ -2,9 +2,53 @@ import { createLocalizationPipeKeyGenerator, createLocalizer, createLocalizerWithFallback, + getLocaleDirection, } from '../utils/localization-utils'; describe('Localization Utils', () => { + describe('#getLocaleDirection', () => { + test.each` + locale | expected + ${undefined} | ${'ltr'} + ${null} | ${'ltr'} + ${''} | ${'ltr'} + ${'en'} | ${'ltr'} + ${'en-US'} | ${'ltr'} + ${'pt'} | ${'ltr'} + ${'pt-PT'} | ${'ltr'} + ${'ar'} | ${'rtl'} + ${'ar-AE'} | ${'rtl'} + ${'ar-QA'} | ${'rtl'} + ${'ckb'} | ${'rtl'} + ${'ckb-IR'} | ${'rtl'} + ${'fa'} | ${'rtl'} + ${'fa-AF'} | ${'rtl'} + ${'he'} | ${'rtl'} + ${'ks'} | ${'rtl'} + ${'ksb'} | ${'ltr'} + ${'ksf'} | ${'ltr'} + ${'ksh'} | ${'ltr'} + ${'lrc'} | ${'rtl'} + ${'lrc-IQ'} | ${'rtl'} + ${'mzn'} | ${'rtl'} + ${'pa'} | ${'ltr'} + ${'pa-Arab'} | ${'rtl'} + ${'ps'} | ${'rtl'} + ${'ps-PK'} | ${'rtl'} + ${'sd'} | ${'rtl'} + ${'ug'} | ${'rtl'} + ${'ur'} | ${'rtl'} + ${'ur-IN'} | ${'rtl'} + ${'uz'} | ${'ltr'} + ${'uz-Arab'} | ${'rtl'} + ${'yi'} | ${'rtl'} + ${'zh'} | ${'ltr'} + ${'zh-Hans'} | ${'ltr'} + `('should return $expected when $locale is given as parameter', ({ locale, expected }) => { + expect(getLocaleDirection(locale)).toBe(expected); + }); + }); + describe('#createLocalizer', () => { const localize = createLocalizer({ values: { foo: { bar: 'baz' }, x: { y: 'z' } }, diff --git a/npm/ng-packs/packages/core/src/lib/utils/localization-utils.ts b/npm/ng-packs/packages/core/src/lib/utils/localization-utils.ts index ed094f7fc7..09478c8ed8 100644 --- a/npm/ng-packs/packages/core/src/lib/utils/localization-utils.ts +++ b/npm/ng-packs/packages/core/src/lib/utils/localization-utils.ts @@ -1,5 +1,14 @@ import { ApplicationConfiguration } from '../models/application-configuration'; +// This will not be necessary when only Angukar 9.1+ is supported +export function getLocaleDirection(locale: string): 'ltr' | 'rtl' { + return /^(ar(-[A-Z]{2})?|ckb(-IR)?|fa(-AF)?|he|ks|lrc(-IQ)?|mzn|pa-Arab|ps(-PK)?|sd|ug|ur(-IN)?|uz-Arab|yi)$/.test( + locale, + ) + ? 'rtl' + : 'ltr'; +} + export function createLocalizer(localization: ApplicationConfiguration.Localization) { return (resourceName: string, key: string, defaultValue: string) => { if (resourceName === '_') return key; From 9a0c250374195522cc87cfde7437dfa26bce5a93 Mon Sep 17 00:00:00 2001 From: Arman Ozak Date: Thu, 28 May 2020 23:09:44 +0300 Subject: [PATCH 02/22] feat: keep element as public property on loading strategy --- .../packages/core/src/lib/strategies/loading.strategy.ts | 8 +++++--- .../packages/core/src/lib/tests/loading.strategy.spec.ts | 2 ++ 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/npm/ng-packs/packages/core/src/lib/strategies/loading.strategy.ts b/npm/ng-packs/packages/core/src/lib/strategies/loading.strategy.ts index be89a751ed..f1f3abe30c 100644 --- a/npm/ng-packs/packages/core/src/lib/strategies/loading.strategy.ts +++ b/npm/ng-packs/packages/core/src/lib/strategies/loading.strategy.ts @@ -5,6 +5,8 @@ import { CrossOriginStrategy, CROSS_ORIGIN_STRATEGY } from './cross-origin.strat import { DomStrategy, DOM_STRATEGY } from './dom.strategy'; export abstract class LoadingStrategy { + element: T; + constructor( public path: string, protected domStrategy: DomStrategy = DOM_STRATEGY.AppendToHead(), @@ -14,10 +16,10 @@ export abstract class LoadingStrategy(): Observable { + this.element = this.createElement(); + return of(null).pipe( - switchMap(() => - fromLazyLoad(this.createElement(), this.domStrategy, this.crossOriginStrategy), - ), + switchMap(() => fromLazyLoad(this.element, this.domStrategy, this.crossOriginStrategy)), ); } } diff --git a/npm/ng-packs/packages/core/src/lib/tests/loading.strategy.spec.ts b/npm/ng-packs/packages/core/src/lib/tests/loading.strategy.spec.ts index 4b950cf533..4215e6eee5 100644 --- a/npm/ng-packs/packages/core/src/lib/tests/loading.strategy.spec.ts +++ b/npm/ng-packs/packages/core/src/lib/tests/loading.strategy.spec.ts @@ -39,6 +39,7 @@ describe('ScriptLoadingStrategy', () => { const strategy = new ScriptLoadingStrategy(path, domStrategy, crossOriginStrategy); strategy.createStream().subscribe(event => { + expect(strategy.element.tagName).toBe('SCRIPT'); expect(event.detail.crossOrigin).toBe('use-credentials'); done(); }); @@ -78,6 +79,7 @@ describe('StyleLoadingStrategy', () => { const strategy = new StyleLoadingStrategy(path, domStrategy, crossOriginStrategy); strategy.createStream().subscribe(event => { + expect(strategy.element.tagName).toBe('LINK'); expect(event.detail.crossOrigin).toBe('use-credentials'); done(); }); From bb9851cb1525733aace42081a6b4a70b622c269a Mon Sep 17 00:00:00 2001 From: Arman Ozak Date: Thu, 28 May 2020 23:10:06 +0300 Subject: [PATCH 03/22] feat: add remove method to lazy loading service --- .../src/lib/services/lazy-load.service.ts | 13 +++++++-- .../src/lib/tests/lazy-load.service.spec.ts | 28 ++++++++++++++++++- 2 files changed, 38 insertions(+), 3 deletions(-) diff --git a/npm/ng-packs/packages/core/src/lib/services/lazy-load.service.ts b/npm/ng-packs/packages/core/src/lib/services/lazy-load.service.ts index cec9d4a6cf..d58788d878 100644 --- a/npm/ng-packs/packages/core/src/lib/services/lazy-load.service.ts +++ b/npm/ng-packs/packages/core/src/lib/services/lazy-load.service.ts @@ -8,7 +8,7 @@ import { uuid } from '../utils'; providedIn: 'root', }) export class LazyLoadService { - readonly loaded = new Set(); + readonly loaded = new Map(); loadedLibraries: { [url: string]: ReplaySubject } = {}; @@ -45,7 +45,7 @@ export class LazyLoadService { throwError(new CustomEvent('error')), ), ), - tap(() => this.loaded.add(strategy.path)), + tap(() => this.loaded.set(strategy.path, strategy.element)), delay(100), shareReplay({ bufferSize: 1, refCount: true }), ); @@ -113,4 +113,13 @@ export class LazyLoadService { }); }); } + + remove(path: string): boolean { + if (!this.loaded.has(path)) return false; + + const element = this.loaded.get(path); + element.parentNode.removeChild(element); + this.loaded.delete(path); + return true; + } } diff --git a/npm/ng-packs/packages/core/src/lib/tests/lazy-load.service.spec.ts b/npm/ng-packs/packages/core/src/lib/tests/lazy-load.service.spec.ts index 3596da2591..4868fb78f3 100644 --- a/npm/ng-packs/packages/core/src/lib/tests/lazy-load.service.spec.ts +++ b/npm/ng-packs/packages/core/src/lib/tests/lazy-load.service.spec.ts @@ -49,7 +49,7 @@ describe('LazyLoadService', () => { it('should emit a custom load event if loaded if resource is loaded before', done => { const loadEvent = new CustomEvent('load'); - service.loaded.add(strategy.path); + service.loaded.set(strategy.path, null); service.load(strategy).subscribe(event => { expect(event).toEqual(loadEvent); @@ -57,6 +57,32 @@ describe('LazyLoadService', () => { }); }); }); + + describe('#remove', () => { + const service = new LazyLoadService(); + + it('should remove an already lazy loaded element and return true', () => { + const script = document.createElement('script'); + document.body.appendChild(script); + service.loaded.set('x', script); + + expect(document.body.lastElementChild).toBe(script); + + const result = service.remove('x'); + + expect(document.body.lastElementChild).toBeNull(); + expect(service.loaded.has('x')).toBe(false); + expect(result).toBe(true); + }); + + it('should return false when path not found', () => { + service.loaded.set('foo', null); + + const result = service.remove('bar'); + + expect(result).toBe(false); + }); + }); }); describe('LazyLoadService (Deprecated)', () => { From f48887d1f23d15971a959714069a26d87876314d Mon Sep 17 00:00:00 2001 From: Arman Ozak Date: Thu, 28 May 2020 23:11:17 +0300 Subject: [PATCH 04/22] feat: add a LocaleDirection type --- npm/ng-packs/packages/theme-shared/src/lib/models/common.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/npm/ng-packs/packages/theme-shared/src/lib/models/common.ts b/npm/ng-packs/packages/theme-shared/src/lib/models/common.ts index d75c854476..09171179c4 100644 --- a/npm/ng-packs/packages/theme-shared/src/lib/models/common.ts +++ b/npm/ng-packs/packages/theme-shared/src/lib/models/common.ts @@ -14,3 +14,5 @@ export interface HttpErrorConfig { hideCloseIcon?: boolean; }; } + +export type LocaleDirection = 'ltr' | 'rtl'; From fe8ea095640e617aa0800d012661bc1ce7eed680 Mon Sep 17 00:00:00 2001 From: Arman Ozak Date: Thu, 28 May 2020 23:16:09 +0300 Subject: [PATCH 05/22] build: create bootstrap style chunks for ltr and rtl --- npm/ng-packs/angular.json | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/npm/ng-packs/angular.json b/npm/ng-packs/angular.json index 39a10f827b..bf45811340 100644 --- a/npm/ng-packs/angular.json +++ b/npm/ng-packs/angular.json @@ -440,18 +440,27 @@ "assets": ["apps/dev-app/src/favicon.ico", "apps/dev-app/src/assets"], "extractCss": true, "styles": [ - "apps/dev-app/src/styles.scss", - "node_modules/bootstrap/dist/css/bootstrap.min.css", + { + "input": "node_modules/@abp/ng.theme.shared/styles/bootstrap-rtl.min.css", + "inject": false, + "bundleName": "bootstrap-rtl.min" + }, + { + "input": "node_modules/bootstrap/dist/css/bootstrap.min.css", + "inject": true, + "bundleName": "bootstrap-ltr.min" + }, { "input": "node_modules/@fortawesome/fontawesome-free/css/all.min.css", - "lazy": true, + "inject": false, "bundleName": "fontawesome-all.min" }, { "input": "node_modules/@fortawesome/fontawesome-free/css/v4-shims.min.css", - "lazy": true, + "inject": false, "bundleName": "fontawesome-v4-shims.min" - } + }, + "apps/dev-app/src/styles.scss" ], "scripts": [] }, From 832c47e0f1dcf07901409f16ffc7240386696f79 Mon Sep 17 00:00:00 2001 From: Arman Ozak Date: Thu, 28 May 2020 23:20:48 +0300 Subject: [PATCH 06/22] feat: inject fontawesome chunks instead of lazy loading --- npm/ng-packs/angular.json | 4 ++-- .../apps/dev-app/src/app/app.component.ts | 19 ++----------------- 2 files changed, 4 insertions(+), 19 deletions(-) diff --git a/npm/ng-packs/angular.json b/npm/ng-packs/angular.json index bf45811340..2b876fe21c 100644 --- a/npm/ng-packs/angular.json +++ b/npm/ng-packs/angular.json @@ -452,12 +452,12 @@ }, { "input": "node_modules/@fortawesome/fontawesome-free/css/all.min.css", - "inject": false, + "inject": true, "bundleName": "fontawesome-all.min" }, { "input": "node_modules/@fortawesome/fontawesome-free/css/v4-shims.min.css", - "inject": false, + "inject": true, "bundleName": "fontawesome-v4-shims.min" }, "apps/dev-app/src/styles.scss" diff --git a/npm/ng-packs/apps/dev-app/src/app/app.component.ts b/npm/ng-packs/apps/dev-app/src/app/app.component.ts index 8187365734..bf2a27962a 100644 --- a/npm/ng-packs/apps/dev-app/src/app/app.component.ts +++ b/npm/ng-packs/apps/dev-app/src/app/app.component.ts @@ -1,6 +1,4 @@ -import { LazyLoadService, LOADING_STRATEGY } from '@abp/ng.core'; -import { Component, OnInit } from '@angular/core'; -import { forkJoin } from 'rxjs'; +import { Component } from '@angular/core'; @Component({ selector: 'app-root', @@ -9,17 +7,4 @@ import { forkJoin } from 'rxjs'; `, }) -export class AppComponent implements OnInit { - constructor(private lazyLoadService: LazyLoadService) {} - - ngOnInit() { - forkJoin( - this.lazyLoadService.load( - LOADING_STRATEGY.PrependAnonymousStyleToHead('fontawesome-v4-shims.min.css'), - ), - this.lazyLoadService.load( - LOADING_STRATEGY.PrependAnonymousStyleToHead('fontawesome-all.min.css'), - ), - ).subscribe(); - } -} +export class AppComponent {} From 80c530f803be7d4be2394f5baec4491a810db205 Mon Sep 17 00:00:00 2001 From: Arman Ozak Date: Thu, 28 May 2020 23:23:08 +0300 Subject: [PATCH 07/22] feat: apply style chunk changes to template --- templates/app/angular/angular.json | 15 ++++++++++----- .../app/angular/src/app/app.component.ts | 19 ++----------------- 2 files changed, 12 insertions(+), 22 deletions(-) diff --git a/templates/app/angular/angular.json b/templates/app/angular/angular.json index 567b8f95c7..4f176a44b8 100644 --- a/templates/app/angular/angular.json +++ b/templates/app/angular/angular.json @@ -26,22 +26,27 @@ "extractCss": true, "assets": ["src/favicon.ico", "src/assets"], "styles": [ + { + "input": "node_modules/@abp/ng.theme.shared/styles/bootstrap-rtl.min.css", + "inject": false, + "bundleName": "bootstrap-rtl.min" + }, { "input": "node_modules/bootstrap/dist/css/bootstrap.min.css", "inject": true, - "bundleName": "bootstrap.min" + "bundleName": "bootstrap-ltr.min" }, - "src/styles.scss", { "input": "node_modules/@fortawesome/fontawesome-free/css/all.min.css", - "inject": false, + "inject": true, "bundleName": "fontawesome-all.min" }, { "input": "node_modules/@fortawesome/fontawesome-free/css/v4-shims.min.css", - "inject": false, + "inject": true, "bundleName": "fontawesome-v4-shims.min" - } + }, + "apps/dev-app/src/styles.scss" ], "scripts": [] }, diff --git a/templates/app/angular/src/app/app.component.ts b/templates/app/angular/src/app/app.component.ts index 963806c4f4..bf2a27962a 100644 --- a/templates/app/angular/src/app/app.component.ts +++ b/templates/app/angular/src/app/app.component.ts @@ -1,6 +1,4 @@ -import { LazyLoadService, LOADING_STRATEGY } from '@abp/ng.core'; -import { Component, OnInit } from '@angular/core'; -import { forkJoin } from 'rxjs'; +import { Component } from '@angular/core'; @Component({ selector: 'app-root', @@ -9,17 +7,4 @@ import { forkJoin } from 'rxjs'; `, }) -export class AppComponent implements OnInit { - constructor(private lazyLoadService: LazyLoadService) {} - - ngOnInit() { - forkJoin( - this.lazyLoadService.load( - LOADING_STRATEGY.PrependAnonymousStyleToHead('fontawesome-v4-shims.min.css') - ), - this.lazyLoadService.load( - LOADING_STRATEGY.PrependAnonymousStyleToHead('fontawesome-all.min.css') - ) - ).subscribe(); - } -} +export class AppComponent {} From cb2533f4299e67c2cdd47c08655e2456016b68b6 Mon Sep 17 00:00:00 2001 From: Arman Ozak Date: Thu, 28 May 2020 23:24:33 +0300 Subject: [PATCH 08/22] feat: add a lazy styles token --- npm/ng-packs/packages/theme-shared/src/lib/tokens/index.ts | 1 + .../packages/theme-shared/src/lib/tokens/lazy-styles.token.ts | 3 +++ 2 files changed, 4 insertions(+) create mode 100644 npm/ng-packs/packages/theme-shared/src/lib/tokens/lazy-styles.token.ts diff --git a/npm/ng-packs/packages/theme-shared/src/lib/tokens/index.ts b/npm/ng-packs/packages/theme-shared/src/lib/tokens/index.ts index 1bdd74c53e..a923c8fc23 100644 --- a/npm/ng-packs/packages/theme-shared/src/lib/tokens/index.ts +++ b/npm/ng-packs/packages/theme-shared/src/lib/tokens/index.ts @@ -1,2 +1,3 @@ export * from './append-content.token'; export * from './http-error.token'; +export * from './lazy-styles.token'; diff --git a/npm/ng-packs/packages/theme-shared/src/lib/tokens/lazy-styles.token.ts b/npm/ng-packs/packages/theme-shared/src/lib/tokens/lazy-styles.token.ts new file mode 100644 index 0000000000..73ca3044fc --- /dev/null +++ b/npm/ng-packs/packages/theme-shared/src/lib/tokens/lazy-styles.token.ts @@ -0,0 +1,3 @@ +import { InjectionToken } from '@angular/core'; + +export const LAZY_STYLES = new InjectionToken('LAZY_STYLES'); From bf90091a7e024a89f1e7b5cb00c9620c759db47f Mon Sep 17 00:00:00 2001 From: Arman Ozak Date: Thu, 28 May 2020 23:25:24 +0300 Subject: [PATCH 09/22] feat: add rtl fixes and bootstrap pattern --- .../packages/theme-shared/src/lib/constants/styles.ts | 11 +++++++++++ npm/ng-packs/packages/theme-shared/src/public-api.ts | 1 + 2 files changed, 12 insertions(+) diff --git a/npm/ng-packs/packages/theme-shared/src/lib/constants/styles.ts b/npm/ng-packs/packages/theme-shared/src/lib/constants/styles.ts index 4458117d16..f2b405b744 100644 --- a/npm/ng-packs/packages/theme-shared/src/lib/constants/styles.ts +++ b/npm/ng-packs/packages/theme-shared/src/lib/constants/styles.ts @@ -13,6 +13,15 @@ export default ` text-align: right; } +[dir=rtl] .data-tables-filter { + text-align: left; +} + +[dir=rtl] .dropdown-menu-right { + left: 0; + right: auto; +} + .pointer { cursor: pointer; } @@ -155,3 +164,5 @@ export default ` /* Date: Thu, 28 May 2020 23:29:34 +0300 Subject: [PATCH 10/22] feat: add rtl fixes to basic theme styles --- .../packages/theme-basic/src/lib/constants/styles.ts | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/npm/ng-packs/packages/theme-basic/src/lib/constants/styles.ts b/npm/ng-packs/packages/theme-basic/src/lib/constants/styles.ts index 0d9ebcc300..0fa787953e 100644 --- a/npm/ng-packs/packages/theme-basic/src/lib/constants/styles.ts +++ b/npm/ng-packs/packages/theme-basic/src/lib/constants/styles.ts @@ -2,7 +2,6 @@ export default ` .content-header-title { font-size: 24px; } - .entry-row { margin-bottom: 15px; } @@ -26,17 +25,25 @@ export default ` border-radius: 0.25rem; min-height: 44px; } +[dir=rtl] .navbar .dropdown-menu a { + text-align: right; +} .navbar .dropdown-submenu a::after { transform: rotate(-90deg); position: absolute; right: 16px; top: 18px; } +[dir=rtl] .navbar .dropdown-submenu a::after { + transform: rotate(90deg); + left: 16px; + right: auto; + top: 20px; +} .navbar .dropdown-submenu .dropdown-menu { top: 0; left: 100%; } - .card-header .btn { padding: 2px 6px; } From 03c077ee2f403ba03b768ed7fcca6736665e1e0b Mon Sep 17 00:00:00 2001 From: Arman Ozak Date: Thu, 28 May 2020 23:30:40 +0300 Subject: [PATCH 11/22] feat: add a lazy style handler and provider factory --- .../theme-shared/src/lib/handlers/index.ts | 1 + .../src/lib/handlers/lazy-style.handler.ts | 96 +++++++++++++++++++ .../src/lib/tests/lazy-style.handler.spec.ts | 66 +++++++++++++ 3 files changed, 163 insertions(+) create mode 100644 npm/ng-packs/packages/theme-shared/src/lib/handlers/lazy-style.handler.ts create mode 100644 npm/ng-packs/packages/theme-shared/src/lib/tests/lazy-style.handler.spec.ts diff --git a/npm/ng-packs/packages/theme-shared/src/lib/handlers/index.ts b/npm/ng-packs/packages/theme-shared/src/lib/handlers/index.ts index 7b46abdd69..11eb07398e 100644 --- a/npm/ng-packs/packages/theme-shared/src/lib/handlers/index.ts +++ b/npm/ng-packs/packages/theme-shared/src/lib/handlers/index.ts @@ -1 +1,2 @@ export * from './error.handler'; +export * from './lazy-style.handler'; diff --git a/npm/ng-packs/packages/theme-shared/src/lib/handlers/lazy-style.handler.ts b/npm/ng-packs/packages/theme-shared/src/lib/handlers/lazy-style.handler.ts new file mode 100644 index 0000000000..11fe0f0da8 --- /dev/null +++ b/npm/ng-packs/packages/theme-shared/src/lib/handlers/lazy-style.handler.ts @@ -0,0 +1,96 @@ +import { + getLocaleDirection, + LazyLoadService, + LOADING_STRATEGY, + LocalizationService, +} from '@abp/ng.core'; +import { Injectable, Injector } from '@angular/core'; +import { map, startWith } from 'rxjs/operators'; +import { BOOTSTRAP } from '../constants/styles'; +import { LocaleDirection } from '../models/common'; +import { LAZY_STYLES } from '../tokens/lazy-styles.token'; + +@Injectable({ + providedIn: 'root', +}) +export class LazyStyleHandler { + private lazyLoad: LazyLoadService; + private styles: string[]; + private _dir: LocaleDirection = 'ltr'; + + set dir(dir: LocaleDirection) { + if (dir === this._dir) return; + + this.switchCSS(dir); + this.setHtmlDir(dir); + this._dir = dir; + } + + get dir(): LocaleDirection { + return this._dir; + } + + constructor(injector: Injector) { + this.setStyles(injector); + this.setLazyLoad(injector); + this.listenToLanguageChanges(injector); + } + + private getLoadedBootstrap(): LoadedStyle { + const href = createLazyStyleHref(BOOTSTRAP, this.dir); + const selector = `[href$="${href}"]`; + const link = document.querySelector(selector); + return { href, link }; + } + + private listenToLanguageChanges(injector: Injector) { + const l10n = injector.get(LocalizationService); + + // will always listen, no need to unsubscribe + l10n.languageChange + .pipe( + map(({ payload }) => payload), + startWith(l10n.currentLang), + ) + .subscribe(locale => { + this.dir = getLocaleDirection(locale); + }); + } + + private setHtmlDir(dir: LocaleDirection) { + document.querySelector('html').dir = dir; + } + + private setLazyLoad(injector: Injector) { + this.lazyLoad = injector.get(LazyLoadService); + const { href, link } = this.getLoadedBootstrap(); + this.lazyLoad.loaded.set(href, link); + } + + private setStyles(injector: Injector) { + this.styles = injector.get(LAZY_STYLES, [BOOTSTRAP]); + } + + private switchCSS(dir: LocaleDirection) { + this.styles.forEach(style => { + const oldHref = createLazyStyleHref(style, this.dir); + const newHref = createLazyStyleHref(style, dir); + + const strategy = LOADING_STRATEGY.PrependAnonymousStyleToHead(newHref); + this.lazyLoad.load(strategy).subscribe(() => this.lazyLoad.remove(oldHref)); + }); + } +} + +export function createLazyStyleHref(style: string, dir: string): string { + return style.replace(/{{\s*dir\s*}}/g, dir); +} + +export function initLazyStyleHandler(injector: Injector) { + return () => new LazyStyleHandler(injector); +} + +interface LoadedStyle { + href: string; + link: HTMLLinkElement; +} diff --git a/npm/ng-packs/packages/theme-shared/src/lib/tests/lazy-style.handler.spec.ts b/npm/ng-packs/packages/theme-shared/src/lib/tests/lazy-style.handler.spec.ts new file mode 100644 index 0000000000..5836bee474 --- /dev/null +++ b/npm/ng-packs/packages/theme-shared/src/lib/tests/lazy-style.handler.spec.ts @@ -0,0 +1,66 @@ +import { LazyLoadService, LOADING_STRATEGY, LocalizationService } from '@abp/ng.core'; +import { createServiceFactory, SpectatorService } from '@ngneat/spectator/jest'; +import { EMPTY, of } from 'rxjs'; +import { BOOTSTRAP } from '../constants/styles'; +import { createLazyStyleHref, initLazyStyleHandler, LazyStyleHandler } from '../handlers'; + +const languageChange = of({ payload: 'en' }); + +describe('LazyStyleHandler', () => { + let spectator: SpectatorService; + let handler: LazyStyleHandler; + let lazyLoad: LazyLoadService; + + const createService = createServiceFactory({ + service: LazyStyleHandler, + providers: [ + { + provide: LocalizationService, + useValue: { currentLang: 'en', languageChange }, + }, + ], + }); + + beforeEach(() => { + spectator = createService(); + handler = spectator.service; + lazyLoad = handler['lazyLoad']; + }); + + describe('#dir', () => { + it('should initially be "ltr"', () => { + expect(handler.dir).toBe('ltr'); + }); + + it('should set bootstrap to rtl', () => { + const oldHref = createLazyStyleHref(BOOTSTRAP, 'ltr'); + const newHref = createLazyStyleHref(BOOTSTRAP, 'rtl'); + lazyLoad.loaded.set(newHref, null); // avoid actual loading + const load = jest.spyOn(lazyLoad, 'load'); + const remove = jest.spyOn(lazyLoad, 'remove'); + const strategy = LOADING_STRATEGY.PrependAnonymousStyleToHead(newHref); + + handler.dir = 'rtl'; + + expect(load).toHaveBeenCalledWith(strategy); + expect(remove).toHaveBeenCalledWith(oldHref); + }); + }); +}); + +describe('initLazyStyleHandler', () => { + it('should return a LazyStyleHandler factory', () => { + const generator = (function*() { + yield undefined; // LAZY_STYLES + yield { loaded: new Map() }; // LazyLoadService + yield { currentLang: 'en', languageChange: EMPTY }; // LocalizationService + })(); + + const injector = { + get: () => generator.next().value as any, + }; + const factory = initLazyStyleHandler(injector); + + expect(factory()).toBeInstanceOf(LazyStyleHandler); + }); +}); From a7f9ea3f41a2167fa936e8118fe2260ebb66a2ff Mon Sep 17 00:00:00 2001 From: Arman Ozak Date: Thu, 28 May 2020 23:31:11 +0300 Subject: [PATCH 12/22] feat: use initLazyStyleHandler in forRoot of ThemeShared --- .../packages/theme-shared/src/lib/theme-shared.module.ts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/npm/ng-packs/packages/theme-shared/src/lib/theme-shared.module.ts b/npm/ng-packs/packages/theme-shared/src/lib/theme-shared.module.ts index 5d53ed2124..72ec96f569 100644 --- a/npm/ng-packs/packages/theme-shared/src/lib/theme-shared.module.ts +++ b/npm/ng-packs/packages/theme-shared/src/lib/theme-shared.module.ts @@ -22,6 +22,7 @@ import styles from './constants/styles'; import { LoadingDirective } from './directives/loading.directive'; import { TableSortDirective } from './directives/table-sort.directive'; import { ErrorHandler } from './handlers/error.handler'; +import { initLazyStyleHandler } from './handlers/lazy-style.handler'; import { RootParams } from './models/common'; import { THEME_SHARED_APPEND_CONTENT } from './tokens/append-content.token'; import { httpErrorConfigFactory, HTTP_ERROR_CONFIG } from './tokens/http-error.token'; @@ -104,6 +105,12 @@ export class ThemeSharedModule { deps: [THEME_SHARED_APPEND_CONTENT], useFactory: noop, }, + { + provide: APP_INITIALIZER, + multi: true, + deps: [Injector], + useFactory: initLazyStyleHandler, + }, { provide: HTTP_ERROR_CONFIG, useValue: options.httpErrorConfig }, { provide: 'HTTP_ERROR_CONFIG', From 63aa6a6328678e24e237bda30d7a3860e1de0df6 Mon Sep 17 00:00:00 2001 From: Arman Ozak Date: Thu, 28 May 2020 23:31:44 +0300 Subject: [PATCH 13/22] feat: avoid making lazy style handler public --- npm/ng-packs/packages/theme-shared/src/public-api.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/npm/ng-packs/packages/theme-shared/src/public-api.ts b/npm/ng-packs/packages/theme-shared/src/public-api.ts index e201dc92ec..df27b079f4 100644 --- a/npm/ng-packs/packages/theme-shared/src/public-api.ts +++ b/npm/ng-packs/packages/theme-shared/src/public-api.ts @@ -7,7 +7,7 @@ export * from './lib/animations'; export * from './lib/components'; export { BOOTSTRAP } from './lib/constants/styles'; export * from './lib/directives'; -export * from './lib/handlers'; +export { ErrorHandler } from './lib/handlers'; export * from './lib/models'; export * from './lib/services'; export * from './lib/tokens'; From 4e14df048ab3ed199d7adfcaa9433672798a0fc5 Mon Sep 17 00:00:00 2001 From: Arman Ozak Date: Thu, 28 May 2020 23:43:20 +0300 Subject: [PATCH 14/22] feat: avoid null values when removing loaded elements --- .../packages/core/src/lib/services/lazy-load.service.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/npm/ng-packs/packages/core/src/lib/services/lazy-load.service.ts b/npm/ng-packs/packages/core/src/lib/services/lazy-load.service.ts index d58788d878..4ba89949fd 100644 --- a/npm/ng-packs/packages/core/src/lib/services/lazy-load.service.ts +++ b/npm/ng-packs/packages/core/src/lib/services/lazy-load.service.ts @@ -115,9 +115,10 @@ export class LazyLoadService { } remove(path: string): boolean { - if (!this.loaded.has(path)) return false; - const element = this.loaded.get(path); + + if (!element) return false; + element.parentNode.removeChild(element); this.loaded.delete(path); return true; From a2523973c2b23b0b93169cd0cdbf0fa9a91d80cf Mon Sep 17 00:00:00 2001 From: Arman Ozak Date: Thu, 28 May 2020 23:48:13 +0300 Subject: [PATCH 15/22] fix: use important to override text-left class --- npm/ng-packs/packages/theme-basic/src/lib/constants/styles.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/npm/ng-packs/packages/theme-basic/src/lib/constants/styles.ts b/npm/ng-packs/packages/theme-basic/src/lib/constants/styles.ts index 0fa787953e..2de9931873 100644 --- a/npm/ng-packs/packages/theme-basic/src/lib/constants/styles.ts +++ b/npm/ng-packs/packages/theme-basic/src/lib/constants/styles.ts @@ -26,7 +26,7 @@ export default ` min-height: 44px; } [dir=rtl] .navbar .dropdown-menu a { - text-align: right; + text-align: right!important; } .navbar .dropdown-submenu a::after { transform: rotate(-90deg); From 79ee6297b627c6ffb92ad79327580bd9f63413dc Mon Sep 17 00:00:00 2001 From: Arman Ozak Date: Fri, 29 May 2020 00:08:58 +0300 Subject: [PATCH 16/22] docs: add how to migrate a project for RTL support --- docs/en/UI/Angular/Localization.md | 62 ++++++++++++++++++++++++++++++ 1 file changed, 62 insertions(+) diff --git a/docs/en/UI/Angular/Localization.md b/docs/en/UI/Angular/Localization.md index d627f466ba..c8b73b08f3 100644 --- a/docs/en/UI/Angular/Localization.md +++ b/docs/en/UI/Angular/Localization.md @@ -130,6 +130,68 @@ this.store.selectSnapshot( Localization resources are stored in the `localization` property of `ConfigState`. +## RTL Support + +As of v2.9 ABP has RTL support. If you are generating a new project with v2.9 and above, everything is set, you do not need to do any changes. If you are migrating your project from an earlier version, please follow the 2 steps below: + +#### Step 1. Create Chunks for Bootstrap LTR and RTL + +Find [styles configuration in angular.json](https://angular.io/guide/workspace-config#style-script-config) and make sure the chunks in your project has `bootstrap-rtl.min` and `bootstrap-ltr.min` as shown below. + +```json +{ + "projects": { + "MyProjectName": { + "architect": { + "build": { + "options": { + "styles": [ + { + "input": "node_modules/@abp/ng.theme.shared/styles/bootstrap-rtl.min.css", + "inject": false, + "bundleName": "bootstrap-rtl.min" + }, + { + "input": "node_modules/bootstrap/dist/css/bootstrap.min.css", + "inject": true, + "bundleName": "bootstrap-ltr.min" + }, + { + "input": "node_modules/@fortawesome/fontawesome-free/css/all.min.css", + "inject": true, + "bundleName": "fontawesome-all.min" + }, + { + "input": "node_modules/@fortawesome/fontawesome-free/css/v4-shims.min.css", + "inject": true, + "bundleName": "fontawesome-v4-shims.min" + }, + "apps/dev-app/src/styles.scss" + ], + } + } + } + } + } +} + +#### Step 2. Clear Lazy Loaded Fontawesome in AppComponent + +If you have created and injected chunks for Fontawesome as seen above, you no longer need the lazy loading in the `AppComponent` which was implemented before v2.9. Simply remove them. The `AppComponent` in the template of the new version looks like this: + +```js +import { Component } from '@angular/core'; + +@Component({ + selector: 'app-root', + template: ` + + + `, +}) +export class AppComponent {} +``` + ## See Also From 286412b7d2a0b263452eb903da011fd44cdebac5 Mon Sep 17 00:00:00 2001 From: Arman Ozak Date: Fri, 29 May 2020 00:24:15 +0300 Subject: [PATCH 17/22] refactor: remove unnecessary RTL style --- .../packages/theme-shared/src/lib/constants/styles.ts | 5 ----- 1 file changed, 5 deletions(-) diff --git a/npm/ng-packs/packages/theme-shared/src/lib/constants/styles.ts b/npm/ng-packs/packages/theme-shared/src/lib/constants/styles.ts index f2b405b744..a691f96ead 100644 --- a/npm/ng-packs/packages/theme-shared/src/lib/constants/styles.ts +++ b/npm/ng-packs/packages/theme-shared/src/lib/constants/styles.ts @@ -17,11 +17,6 @@ export default ` text-align: left; } -[dir=rtl] .dropdown-menu-right { - left: 0; - right: auto; -} - .pointer { cursor: pointer; } From 1b727aa10318e76415ab189c7f1084ada838620d Mon Sep 17 00:00:00 2001 From: Arman Ozak Date: Fri, 29 May 2020 09:59:44 +0300 Subject: [PATCH 18/22] fix: refer to correct styles.scss in angular.json --- templates/app/angular/angular.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/templates/app/angular/angular.json b/templates/app/angular/angular.json index 4f176a44b8..dfcaa5b9d3 100644 --- a/templates/app/angular/angular.json +++ b/templates/app/angular/angular.json @@ -46,7 +46,7 @@ "inject": true, "bundleName": "fontawesome-v4-shims.min" }, - "apps/dev-app/src/styles.scss" + "src/styles.scss" ], "scripts": [] }, From 59beff177c630a461c5d9a9c4cfbd303a8b73d78 Mon Sep 17 00:00:00 2001 From: Arman Ozak Date: Fri, 29 May 2020 11:49:34 +0300 Subject: [PATCH 19/22] feat: add direction to document body instead of html --- .../theme-shared/src/lib/handlers/lazy-style.handler.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/npm/ng-packs/packages/theme-shared/src/lib/handlers/lazy-style.handler.ts b/npm/ng-packs/packages/theme-shared/src/lib/handlers/lazy-style.handler.ts index 11fe0f0da8..f1688502c5 100644 --- a/npm/ng-packs/packages/theme-shared/src/lib/handlers/lazy-style.handler.ts +++ b/npm/ng-packs/packages/theme-shared/src/lib/handlers/lazy-style.handler.ts @@ -22,7 +22,7 @@ export class LazyStyleHandler { if (dir === this._dir) return; this.switchCSS(dir); - this.setHtmlDir(dir); + this.setBodyDir(dir); this._dir = dir; } @@ -53,12 +53,13 @@ export class LazyStyleHandler { startWith(l10n.currentLang), ) .subscribe(locale => { + if (locale === 'tr') locale = 'ar'; this.dir = getLocaleDirection(locale); }); } - private setHtmlDir(dir: LocaleDirection) { - document.querySelector('html').dir = dir; + private setBodyDir(dir: LocaleDirection) { + document.body.dir = dir; } private setLazyLoad(injector: Injector) { From 990c54a3fe90e46563bda39da3f064266736671d Mon Sep 17 00:00:00 2001 From: Arman Ozak Date: Fri, 29 May 2020 11:50:11 +0300 Subject: [PATCH 20/22] feat: calculate table column position when RTL --- .../lib/components/table/table.component.html | 4 ++-- .../lib/components/table/table.component.ts | 20 +++++++++++++++++-- 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/npm/ng-packs/packages/theme-shared/src/lib/components/table/table.component.html b/npm/ng-packs/packages/theme-shared/src/lib/components/table/table.component.html index eeaf0d40d9..6cbea56304 100644 --- a/npm/ng-packs/packages/theme-shared/src/lib/components/table/table.component.html +++ b/npm/ng-packs/packages/theme-shared/src/lib/components/table/table.component.html @@ -21,7 +21,7 @@
-
+
@@ -31,7 +31,7 @@
diff --git a/npm/ng-packs/packages/theme-shared/src/lib/components/table/table.component.ts b/npm/ng-packs/packages/theme-shared/src/lib/components/table/table.component.ts index da17aab136..15b7b4eda0 100644 --- a/npm/ng-packs/packages/theme-shared/src/lib/components/table/table.component.ts +++ b/npm/ng-packs/packages/theme-shared/src/lib/components/table/table.component.ts @@ -1,9 +1,9 @@ import { - AfterViewInit, Component, ElementRef, EventEmitter, Input, + OnInit, Output, TemplateRef, TrackByFunction, @@ -17,7 +17,7 @@ import { styleUrls: ['table.component.scss'], encapsulation: ViewEncapsulation.None, }) -export class TableComponent { +export class TableComponent implements OnInit { private _totalRecords: number; bodyScrollLeft = 0; @@ -87,4 +87,20 @@ export class TableComponent { trackByFn: TrackByFunction = (_, value) => { return typeof value === 'object' ? value[this.trackingProp] || value : value; }; + + marginCalculator: MarginCalculator; + + ngOnInit() { + this.marginCalculator = document.body.dir === 'rtl' ? rtlCalculator : ltrCalculator; + } +} + +function ltrCalculator(div: HTMLDivElement): string { + return `0 auto 0 -${div.scrollLeft}px`; +} + +function rtlCalculator(div: HTMLDivElement): string { + return `0 ${-(div.scrollWidth - div.clientWidth - div.scrollLeft)}px 0 auto`; } + +type MarginCalculator = (div: HTMLDivElement) => string; From d0236057beaebd002e93974b366fcf1ca668c127 Mon Sep 17 00:00:00 2001 From: Arman Ozak Date: Fri, 29 May 2020 12:02:50 +0300 Subject: [PATCH 21/22] fix: avoid field not allowed after method lint error --- .../theme-shared/src/lib/components/table/table.component.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/npm/ng-packs/packages/theme-shared/src/lib/components/table/table.component.ts b/npm/ng-packs/packages/theme-shared/src/lib/components/table/table.component.ts index 15b7b4eda0..a30b4464fc 100644 --- a/npm/ng-packs/packages/theme-shared/src/lib/components/table/table.component.ts +++ b/npm/ng-packs/packages/theme-shared/src/lib/components/table/table.component.ts @@ -84,12 +84,12 @@ export class TableComponent implements OnInit { return this.value.slice(start, start + this.rows); } + marginCalculator: MarginCalculator; + trackByFn: TrackByFunction = (_, value) => { return typeof value === 'object' ? value[this.trackingProp] || value : value; }; - marginCalculator: MarginCalculator; - ngOnInit() { this.marginCalculator = document.body.dir === 'rtl' ? rtlCalculator : ltrCalculator; } From 99543577efd90b94301403e3df701f594b4fc64b Mon Sep 17 00:00:00 2001 From: Arman Ozak Date: Fri, 29 May 2020 12:27:20 +0300 Subject: [PATCH 22/22] fix: remove mock RTL --- .../packages/theme-shared/src/lib/handlers/lazy-style.handler.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/npm/ng-packs/packages/theme-shared/src/lib/handlers/lazy-style.handler.ts b/npm/ng-packs/packages/theme-shared/src/lib/handlers/lazy-style.handler.ts index f1688502c5..bed1a5bbd5 100644 --- a/npm/ng-packs/packages/theme-shared/src/lib/handlers/lazy-style.handler.ts +++ b/npm/ng-packs/packages/theme-shared/src/lib/handlers/lazy-style.handler.ts @@ -53,7 +53,6 @@ export class LazyStyleHandler { startWith(l10n.currentLang), ) .subscribe(locale => { - if (locale === 'tr') locale = 'ar'; this.dir = getLocaleDirection(locale); }); }