mirror of
https://github.com/Alfresco/alfresco-content-app.git
synced 2025-05-12 17:04:46 +00:00
[MOBILEAPPS-1654] Open in App pop-up implementation in android and iphone (#2889)
* Open in App pop up implementation * review comments addressed * unit test cases added for open-in-app component and aca-mobile-app-switcher-service and review comments addressed * cspell changes * test case build failing issue resolved * review comments addressed of using specific type and void in functions that do not return anything * remaining review comments fixed for type cases * added check for ipad and ipod * checkForIOSDevice function removed and checked for ios device in same line * spacing issues addressed * missing unit tests added and some review comments addressed * app.config.json file updated * removed configuration variables from default Readme file * used only boolean instead of string conversion in app service file * session storage name change * review comments addressed
This commit is contained in:
parent
6b72ef5b52
commit
c10a1370a4
@ -52,6 +52,8 @@ env:
|
||||
- APP_CONFIG_OAUTH2_CLIENTID=alfresco
|
||||
- APP_CONFIG_PLUGIN_AOS=true
|
||||
- APP_CONFIG_PLUGIN_CONTENT_SERVICE=true
|
||||
- APP_CONFIG_ENABLE_MOBILE_APP_SWITCH=true
|
||||
- APP_CONFIG_SESSION_TIME_FOR_OPEN_APP_DIALOG_DISPLAY_IN_HOURS="12"
|
||||
- APP_CONFIG_OAUTH2_IMPLICIT_FLOW=true
|
||||
- APP_CONFIG_OAUTH2_SILENT_LOGIN=true
|
||||
- APP_CONFIG_OAUTH2_REDIRECT_LOGOUT=/
|
||||
|
@ -36,6 +36,8 @@ ENV APP_CONFIG_OAUTH2_REDIRECT_LOGOUT="/"
|
||||
ENV APP_CONFIG_PLUGIN_AOS=true
|
||||
ENV APP_CONFIG_PLUGIN_FOLDER_RULES=true
|
||||
ENV APP_CONFIG_PLUGIN_CONTENT_SERVICE=true
|
||||
ENV APP_CONFIG_ENABLE_MOBILE_APP_SWITCH=true
|
||||
ENV APP_CONFIG_SESSION_TIME_FOR_OPEN_APP_DIALOG_DISPLAY_IN_HOURS="12"
|
||||
|
||||
COPY docker/default.conf.template /etc/nginx/templates/
|
||||
COPY docker/docker-entrypoint.d/* /docker-entrypoint.d/
|
||||
|
@ -6,6 +6,13 @@
|
||||
"providers": "${APP_CONFIG_PROVIDER}",
|
||||
"authType": "${APP_CONFIG_AUTH_TYPE}",
|
||||
"loginRoute": "login",
|
||||
"mobileAppSwitch": {
|
||||
"enabled" : ${APP_CONFIG_ENABLE_MOBILE_APP_SWITCH},
|
||||
"iphoneUrl": "iosamw://",
|
||||
"androidUrlPart1": "intent:///",
|
||||
"androidUrlPart2": "#Intent;scheme=androidamw;package=com.alfresco.content.app;end",
|
||||
"sessionTimeForOpenAppDialogDisplay": "${APP_CONFIG_SESSION_TIME_FOR_OPEN_APP_DIALOG_DISPLAY_IN_HOURS}"
|
||||
},
|
||||
"plugins": {
|
||||
"aosPlugin": ${APP_CONFIG_PLUGIN_AOS},
|
||||
"contentService": ${APP_CONFIG_PLUGIN_CONTENT_SERVICE},
|
||||
|
@ -17,10 +17,12 @@
|
||||
"ngstack",
|
||||
"sidenav",
|
||||
"injectable",
|
||||
"iosamw",
|
||||
"truthy",
|
||||
"cryptodoc",
|
||||
"mysites",
|
||||
"afts",
|
||||
"androidamw",
|
||||
"classlist",
|
||||
"folderlink",
|
||||
"filelink",
|
||||
|
@ -269,6 +269,9 @@
|
||||
"MESSAGE": "Leaving will remove your access.",
|
||||
"YES_LABEL": "OK",
|
||||
"NO_LABEL": "Cancel"
|
||||
},
|
||||
"MOBILE_APP": {
|
||||
"MOBILE_APP_BUTTON_LABEL": "Open in App"
|
||||
}
|
||||
},
|
||||
"DOCUMENT_LIST": {
|
||||
|
@ -0,0 +1,8 @@
|
||||
<div class="container">
|
||||
<button mat-button (click)="openInApp()" data-automation-id="open-in-app-button">
|
||||
<span>{{ 'APP.DIALOGS.MOBILE_APP.MOBILE_APP_BUTTON_LABEL' }}</span>
|
||||
</button>
|
||||
<button mat-button mat-dialog-close>
|
||||
<mat-icon>close</mat-icon>
|
||||
</button>
|
||||
</div>
|
@ -0,0 +1,13 @@
|
||||
.container{
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.mat-dialog-container{
|
||||
padding: 12px;
|
||||
border-radius: 36px;
|
||||
background-color: var(--theme-blue-button-color);
|
||||
color: var(--theme-about-panel-background-color);
|
||||
}
|
@ -0,0 +1,38 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { MAT_DIALOG_DATA } from '@angular/material/dialog';
|
||||
import { By } from '@angular/platform-browser';
|
||||
import { OpenInAppComponent } from './open-in-app.component';
|
||||
|
||||
describe('OpenInAppComponent', () => {
|
||||
let fixture: ComponentFixture<OpenInAppComponent>;
|
||||
let component: OpenInAppComponent;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
declarations: [OpenInAppComponent],
|
||||
providers: [{ provide: MAT_DIALOG_DATA, useValue: { redirectUrl: 'mockRedirectUrl' } }]
|
||||
}).compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(OpenInAppComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should redirect to app when click on `Open in App` button` ', async () => {
|
||||
let currentLocation: string | string[];
|
||||
const windowStub: Window & typeof globalThis = {
|
||||
location: {
|
||||
set href(value: string | string[]) {
|
||||
currentLocation = value;
|
||||
}
|
||||
}
|
||||
} as Window & typeof globalThis;
|
||||
component.window = windowStub;
|
||||
const saveButton = fixture.debugElement.query(By.css('[data-automation-id="open-in-app-button"]')).nativeElement;
|
||||
saveButton.dispatchEvent(new Event('click'));
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
|
||||
expect(currentLocation).toBe('mockRedirectUrl');
|
||||
});
|
||||
});
|
@ -0,0 +1,54 @@
|
||||
/*!
|
||||
* @license
|
||||
* Alfresco Example Content Application
|
||||
*
|
||||
* Copyright (C) 2005 - 2020 Alfresco Software Limited
|
||||
*
|
||||
* This file is part of the Alfresco Example Content Application.
|
||||
* If the software was purchased under a paid Alfresco license, the terms of
|
||||
* the paid license agreement will prevail. Otherwise, the software is
|
||||
* provided under the following open source license terms:
|
||||
*
|
||||
* The Alfresco Example Content Application is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* The Alfresco Example Content Application is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { Component, Inject, ViewEncapsulation } from '@angular/core';
|
||||
import { MAT_DIALOG_DATA } from '@angular/material/dialog';
|
||||
|
||||
export interface OpenInAppDialogOptions {
|
||||
redirectUrl: string;
|
||||
}
|
||||
@Component({
|
||||
selector: 'aca-open-in-app',
|
||||
templateUrl: './open-in-app.component.html',
|
||||
styleUrls: ['./open-in-app.component.scss'],
|
||||
encapsulation: ViewEncapsulation.None
|
||||
})
|
||||
export class OpenInAppComponent {
|
||||
private redirectUrl: string;
|
||||
public window: Window & typeof globalThis = window;
|
||||
|
||||
constructor(
|
||||
@Inject(MAT_DIALOG_DATA)
|
||||
public data: OpenInAppDialogOptions
|
||||
) {
|
||||
if (data) {
|
||||
this.redirectUrl = data.redirectUrl;
|
||||
}
|
||||
}
|
||||
|
||||
openInApp(): void {
|
||||
this.window.location.href = this.redirectUrl;
|
||||
}
|
||||
}
|
@ -0,0 +1,105 @@
|
||||
/*!
|
||||
* @license
|
||||
* Alfresco Example Content Application
|
||||
*
|
||||
* Copyright (C) 2005 - 2020 Alfresco Software Limited
|
||||
*
|
||||
* This file is part of the Alfresco Example Content Application.
|
||||
* If the software was purchased under a paid Alfresco license, the terms of
|
||||
* the paid license agreement will prevail. Otherwise, the software is
|
||||
* provided under the following open source license terms:
|
||||
*
|
||||
* The Alfresco Example Content Application is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* The Alfresco Example Content Application is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { TestBed } from '@angular/core/testing';
|
||||
import { AppConfigService } from '@alfresco/adf-core';
|
||||
import { LibTestingModule, initialState } from '../testing/lib-testing-module';
|
||||
import { provideMockStore } from '@ngrx/store/testing';
|
||||
import { AcaMobileAppSwitcherService } from './aca-mobile-app-switcher.service';
|
||||
import { MatDialog } from '@angular/material/dialog';
|
||||
|
||||
describe('AcaMobileAppSwitcherService', () => {
|
||||
let appConfig: AppConfigService;
|
||||
let service: AcaMobileAppSwitcherService;
|
||||
|
||||
const mockDialogRef = {
|
||||
close: jasmine.createSpy('close'),
|
||||
open: jasmine.createSpy('open')
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [LibTestingModule],
|
||||
providers: [provideMockStore({ initialState }), { provide: MatDialog, useValue: mockDialogRef }]
|
||||
});
|
||||
appConfig = TestBed.inject(AppConfigService);
|
||||
appConfig.config.mobileAppSwitch = {
|
||||
enabled: true,
|
||||
iphoneUrl: 'iosamw://',
|
||||
androidUrlPart1: 'intent:///',
|
||||
androidUrlPart2: '#Intent;scheme=androidamw;package=com.alfresco.content.app;end',
|
||||
sessionTimeForOpenAppDialogDisplay: 12
|
||||
};
|
||||
service = TestBed.inject(AcaMobileAppSwitcherService);
|
||||
sessionStorage.clear();
|
||||
});
|
||||
|
||||
it('should set the redirectUrl to `iphoneUrl`', () => {
|
||||
spyOnProperty(window.navigator, 'userAgent').and.returnValue('iphone');
|
||||
const url: string = window.location.href;
|
||||
const iphoneUrl: string = appConfig.config.mobileAppSwitch.iphoneUrl + url;
|
||||
service.showAppNotification();
|
||||
expect(service.redirectUrl).toEqual(iphoneUrl);
|
||||
});
|
||||
|
||||
it('should set the redirectUrl to `androidUrl`', () => {
|
||||
spyOnProperty(window.navigator, 'userAgent').and.returnValue('android');
|
||||
const url: string = window.location.href;
|
||||
const androidUrl: string = appConfig.config.mobileAppSwitch.androidUrlPart1 + url + appConfig.config.mobileAppSwitch.androidUrlPart2;
|
||||
service.showAppNotification();
|
||||
expect(service.redirectUrl).toEqual(androidUrl);
|
||||
});
|
||||
|
||||
it('should check if `showAppNotification` function is called', () => {
|
||||
const showAppNotificationSpy: jasmine.Spy<() => void> = spyOn(service, 'showAppNotification');
|
||||
service.checkForMobileApp();
|
||||
expect(showAppNotificationSpy).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should not display `openInApp` dialog box when timeDifference is less than the session time', () => {
|
||||
service.checkForMobileApp();
|
||||
const showAppNotificationSpy: jasmine.Spy<() => void> = spyOn(service, 'showAppNotification');
|
||||
service.checkForMobileApp();
|
||||
expect(showAppNotificationSpy).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should check if `openInApp` dialog box is getting opened with `iphone` url', () => {
|
||||
const openInAppSpy: jasmine.Spy<(redirectUrl: string) => void> = spyOn(service, 'openInApp');
|
||||
const url: string = window.location.href;
|
||||
service.redirectUrl = appConfig.config.mobileAppSwitch.iphoneUrl + url;
|
||||
service.showAppNotification();
|
||||
expect(openInAppSpy).toHaveBeenCalled();
|
||||
expect(mockDialogRef.open).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should check if `openInApp` dialog box is getting opened with `android` url', () => {
|
||||
const openInAppSpy: jasmine.Spy<(redirectUrl: string) => void> = spyOn(service, 'openInApp');
|
||||
const url: string = window.location.href;
|
||||
service.redirectUrl = appConfig.config.mobileAppSwitch.androidUrlPart1 + url + appConfig.config.mobileAppSwitch.androidUrlPart2;
|
||||
service.showAppNotification();
|
||||
expect(openInAppSpy).toHaveBeenCalled();
|
||||
expect(mockDialogRef.open).toHaveBeenCalled();
|
||||
});
|
||||
});
|
@ -0,0 +1,100 @@
|
||||
/*!
|
||||
* @license
|
||||
* Alfresco Example Content Application
|
||||
*
|
||||
* Copyright (C) 2005 - 2020 Alfresco Software Limited
|
||||
*
|
||||
* This file is part of the Alfresco Example Content Application.
|
||||
* If the software was purchased under a paid Alfresco license, the terms of
|
||||
* the paid license agreement will prevail. Otherwise, the software is
|
||||
* provided under the following open source license terms:
|
||||
*
|
||||
* The Alfresco Example Content Application is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* The Alfresco Example Content Application is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { AppConfigService } from '@alfresco/adf-core';
|
||||
import { Injectable } from '@angular/core';
|
||||
import { MatDialog } from '@angular/material/dialog';
|
||||
import { OpenInAppComponent } from '../components/open-in-app/open-in-app.component';
|
||||
|
||||
export interface MobileAppSwitchConfigurationOptions {
|
||||
enabled: string;
|
||||
iphoneUrl: string;
|
||||
androidUrlPart1: string;
|
||||
androidUrlPart2: string;
|
||||
sessionTimeForOpenAppDialogDisplay: string;
|
||||
}
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class AcaMobileAppSwitcherService {
|
||||
private mobileAppSwitchConfig: MobileAppSwitchConfigurationOptions;
|
||||
public redirectUrl: string;
|
||||
|
||||
constructor(private config: AppConfigService, private dialog: MatDialog) {
|
||||
this.mobileAppSwitchConfig = this.config.get<MobileAppSwitchConfigurationOptions>('mobileAppSwitch');
|
||||
}
|
||||
|
||||
checkForMobileApp(): void {
|
||||
const currentTime: number = new Date().getTime();
|
||||
const sessionTime: string = sessionStorage.getItem('mobile_notification_expires_in');
|
||||
|
||||
if (sessionTime !== null) {
|
||||
const sessionConvertedTime: number = parseFloat(sessionTime);
|
||||
const timeDifference: number = (currentTime - sessionConvertedTime) / (1000 * 60 * 60);
|
||||
const sessionTimeForOpenAppDialogDisplay: number = parseFloat(this.mobileAppSwitchConfig.sessionTimeForOpenAppDialogDisplay);
|
||||
|
||||
if (timeDifference > sessionTimeForOpenAppDialogDisplay) {
|
||||
this.showAppNotification();
|
||||
}
|
||||
} else {
|
||||
this.showAppNotification();
|
||||
}
|
||||
}
|
||||
|
||||
showAppNotification(): void {
|
||||
const ua: string = navigator.userAgent.toLowerCase();
|
||||
const isAndroid: boolean = ua.indexOf('android') > -1;
|
||||
const isIOS: boolean = ua.indexOf('iphone') > -1 || ua.indexOf('ipad') > -1 || ua.indexOf('ipod') > -1;
|
||||
const currentUrl: string = window.location.href;
|
||||
const time: number = new Date().getTime();
|
||||
|
||||
sessionStorage.setItem('mobile_notification_expires_in', time.toString());
|
||||
|
||||
if (isIOS === true) {
|
||||
this.redirectUrl = this.mobileAppSwitchConfig.iphoneUrl + currentUrl;
|
||||
} else if (isAndroid === true) {
|
||||
this.redirectUrl = this.mobileAppSwitchConfig.androidUrlPart1 + currentUrl + this.mobileAppSwitchConfig.androidUrlPart2;
|
||||
}
|
||||
|
||||
if (this.redirectUrl !== undefined && this.redirectUrl !== null) {
|
||||
this.openInApp(this.redirectUrl);
|
||||
}
|
||||
}
|
||||
|
||||
openInApp(redirectUrl: string): void {
|
||||
this.dialog.open(OpenInAppComponent, {
|
||||
data: {
|
||||
redirectUrl
|
||||
},
|
||||
width: '75%',
|
||||
role: 'dialog',
|
||||
position: { bottom: '50px' }
|
||||
});
|
||||
}
|
||||
|
||||
reset(): void {
|
||||
sessionStorage.removeItem('mobile_notification_expires_in');
|
||||
}
|
||||
}
|
@ -51,6 +51,8 @@ import { TranslateService } from '@ngx-translate/core';
|
||||
import { TranslateServiceMock } from '../testing/translation.service';
|
||||
import { RouterTestingModule } from '@angular/router/testing';
|
||||
import { RepositoryInfo } from '@alfresco/js-api';
|
||||
import { AcaMobileAppSwitcherService } from './aca-mobile-app-switcher.service';
|
||||
import { MatDialogModule } from '@angular/material/dialog';
|
||||
|
||||
describe('AppService', () => {
|
||||
let service: AppService;
|
||||
@ -70,10 +72,11 @@ describe('AppService', () => {
|
||||
let groupService: GroupService;
|
||||
let storeInitialAppData: any;
|
||||
let store: MockStore<AppStore>;
|
||||
let acaMobileAppSwitcherService: AcaMobileAppSwitcherService;
|
||||
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [HttpClientModule, RouterTestingModule.withRoutes([])],
|
||||
imports: [HttpClientModule, RouterTestingModule.withRoutes([]), MatDialogModule],
|
||||
providers: [
|
||||
CommonModule,
|
||||
SearchQueryBuilderService,
|
||||
@ -138,6 +141,7 @@ describe('AppService', () => {
|
||||
storeInitialAppData = TestBed.inject(STORE_INITIAL_APP_DATA);
|
||||
store = TestBed.inject(MockStore);
|
||||
auth = TestBed.inject(AuthenticationService);
|
||||
acaMobileAppSwitcherService = TestBed.inject(AcaMobileAppSwitcherService);
|
||||
|
||||
service = new AppService(
|
||||
userPreferencesService,
|
||||
@ -155,7 +159,8 @@ describe('AppService', () => {
|
||||
groupService,
|
||||
overlayContainer,
|
||||
storeInitialAppData,
|
||||
searchQueryBuilderService
|
||||
searchQueryBuilderService,
|
||||
acaMobileAppSwitcherService
|
||||
);
|
||||
});
|
||||
|
||||
@ -182,7 +187,8 @@ describe('AppService', () => {
|
||||
groupService,
|
||||
overlayContainer,
|
||||
storeInitialAppData,
|
||||
searchQueryBuilderService
|
||||
searchQueryBuilderService,
|
||||
acaMobileAppSwitcherService
|
||||
);
|
||||
|
||||
expect(instance.withCredentials).toBeTruthy();
|
||||
|
@ -56,6 +56,7 @@ import { ContentApiService } from './content-api.service';
|
||||
import { RouterExtensionService } from './router.extension.service';
|
||||
import { Store } from '@ngrx/store';
|
||||
import { DiscoveryEntry, GroupEntry, Group } from '@alfresco/js-api';
|
||||
import { AcaMobileAppSwitcherService } from './aca-mobile-app-switcher.service';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
@ -96,7 +97,8 @@ export class AppService implements OnDestroy {
|
||||
private groupService: GroupService,
|
||||
private overlayContainer: OverlayContainer,
|
||||
@Inject(STORE_INITIAL_APP_DATA) private initialAppState: AppState,
|
||||
searchQueryBuilderService: SearchQueryBuilderService
|
||||
searchQueryBuilderService: SearchQueryBuilderService,
|
||||
private acaMobileAppSwitcherService: AcaMobileAppSwitcherService
|
||||
) {
|
||||
this.ready = new BehaviorSubject(this.authenticationService.isLoggedIn() || this.withCredentials);
|
||||
this.ready$ = this.ready.asObservable();
|
||||
@ -177,6 +179,13 @@ export class AppService implements OnDestroy {
|
||||
});
|
||||
|
||||
this.overlayContainer.getContainerElement().setAttribute('role', 'region');
|
||||
|
||||
const isMobileSwitchEnabled: boolean = this.config.get<boolean>('mobileAppSwitch.enabled', false);
|
||||
if (isMobileSwitchEnabled) {
|
||||
this.acaMobileAppSwitcherService.checkForMobileApp();
|
||||
} else {
|
||||
this.acaMobileAppSwitcherService.reset();
|
||||
}
|
||||
}
|
||||
|
||||
private loadRepositoryStatus() {
|
||||
|
@ -28,10 +28,15 @@ import { ContentApiService } from './services/content-api.service';
|
||||
import { NodePermissionService } from './services/node-permission.service';
|
||||
import { AppService } from './services/app.service';
|
||||
import { ContextActionsModule } from './directives/contextmenu/contextmenu.module';
|
||||
import { OpenInAppComponent } from './components/open-in-app/open-in-app.component';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { MatIconModule } from '@angular/material/icon';
|
||||
import { MatDialogModule } from '@angular/material/dialog';
|
||||
|
||||
@NgModule({
|
||||
imports: [ContextActionsModule],
|
||||
exports: [ContextActionsModule]
|
||||
imports: [ContextActionsModule, MatButtonModule, MatIconModule, MatDialogModule],
|
||||
exports: [ContextActionsModule, MatButtonModule, MatIconModule, MatDialogModule],
|
||||
declarations: [OpenInAppComponent]
|
||||
})
|
||||
export class SharedModule {
|
||||
static forRoot(): ModuleWithProviders<SharedModule> {
|
||||
|
Loading…
x
Reference in New Issue
Block a user