App initialization

Used for internal apps as well as for plugins. The pluginpart is
currently missing, in fact that the main OpenSlides part is more
important. Apps can give their models and mainmenu entries.

Routes are not enabled, because the routes have to be static for webpack
to build the bundles. If we want to keep lazy loading, I see no
possibility to encapsulate the routes from the site-routing module.
This commit is contained in:
FinnStutzenstein 2018-09-20 13:03:51 +02:00
parent b6ad0d759c
commit be9f98cfd0
56 changed files with 441 additions and 169 deletions

View File

@ -1,7 +1,7 @@
// angular modules
import { BrowserModule } from '@angular/platform-browser';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { NgModule } from '@angular/core';
import { NgModule, APP_INITIALIZER } from '@angular/core';
import { HttpClientModule, HttpClient, HttpClientXsrfModule } from '@angular/common/http';
// Elementary App Components
@ -13,6 +13,7 @@ import { CoreModule } from './core/core.module';
import { TranslateModule, TranslateLoader } from '@ngx-translate/core';
import { PruningTranslationLoader } from './core/pruning-loader';
import { LoginModule } from './site/login/login.module';
import { AppLoadService } from './core/services/app-load.service';
/**
* For the translation module. Loads a Custom 'translation loader' and provides it as loader.
@ -21,6 +22,15 @@ import { LoginModule } from './site/login/login.module';
export function HttpLoaderFactory(http: HttpClient): PruningTranslationLoader {
return new PruningTranslationLoader(http);
}
/**
* Returns a function that returns a promis that will be resolved, if all apps are loaded.
* @param appLoadService The service that loads the apps.
*/
export function AppLoaderFactory(appLoadService: AppLoadService): () => Promise<void> {
return () => appLoadService.loadApps();
}
/**
* Global App Module. Keep it as clean as possible.
*/
@ -45,6 +55,7 @@ export function HttpLoaderFactory(http: HttpClient): PruningTranslationLoader {
CoreModule,
LoginModule
],
providers: [{ provide: APP_INITIALIZER, useFactory: AppLoaderFactory, deps: [AppLoadService], multi: true }],
bootstrap: [AppComponent]
})
export class AppModule {}

View File

@ -0,0 +1,12 @@
import { TestBed, inject } from '@angular/core/testing';
import { NotifyService } from './notify.service';
describe('NotifyService', () => {
beforeEach(() => {
TestBed.configureTestingModule({
providers: [NotifyService]
});
});
it('should be created', inject([NotifyService], (service: NotifyService) => {
expect(service).toBeTruthy();
}));
});

View File

@ -0,0 +1,61 @@
import { Injectable } from '@angular/core';
import { plugins } from '../../../plugins';
import { CommonAppConfig } from '../../site/common/common.config';
import { ModelConstructor, BaseModel } from '../../shared/models/base/base-model';
import { AppConfig } from '../../site/base/app-config';
import { CollectionStringModelMapperService } from './collectionStringModelMapper.service';
import { MediafileAppConfig } from '../../site/mediafiles/mediafile.config';
import { MotionsAppConfig } from '../../site/motions/motions.config';
import { SettingsAppConfig } from '../../site/settings/settings.config';
import { AgendaAppConfig } from '../../site/agenda/agenda.config';
import { AssignmentsAppConfig } from '../../site/assignments/assignments.config';
import { UsersAppConfig } from '../../site/users/users.config';
import { MainMenuService } from './main-menu.service';
/**
* A list of all app configurations of all delivered apps.
*/
const appConfigs: AppConfig[] = [
CommonAppConfig,
SettingsAppConfig,
AgendaAppConfig,
AssignmentsAppConfig,
MotionsAppConfig,
MediafileAppConfig,
UsersAppConfig
];
/**
* Handles all incoming and outgoing notify messages via {@link WebsocketService}.
*/
@Injectable({
providedIn: 'root'
})
export class AppLoadService {
public constructor(
private modelMapper: CollectionStringModelMapperService,
private mainMenuService: MainMenuService
) {}
public async loadApps(): Promise<void> {
if (plugins.length) {
console.log('plugins: ', plugins);
}
/*for (const pluginName of plugins) {
const plugin = await import('../../../../../plugins/' + pluginName + '/' + pluginName);
plugin.main();
}*/
appConfigs.forEach((config: AppConfig) => {
if (config.models) {
config.models.forEach(entry => {
this.modelMapper.registerCollectionElement(entry.collectionString, entry.model);
});
}
if (config.mainMenuEntries) {
this.mainMenuService.registerEntries(config.mainMenuEntries);
}
});
}
private registerModels(models?: { collectionString: string; model: ModelConstructor<BaseModel> }[]): void {}
}

View File

@ -21,7 +21,11 @@ export class AutoupdateService extends OpenSlidesComponent {
* Constructor to create the AutoupdateService. Calls the constructor of the parent class.
* @param websocketService
*/
public constructor(websocketService: WebsocketService, private DS: DataStoreService) {
public constructor(
websocketService: WebsocketService,
private DS: DataStoreService,
private modelMapper: CollectionStringModelMapperService
) {
super();
websocketService.getOberservable<any>('autoupdate').subscribe(response => {
this.storeResponse(response);
@ -61,7 +65,7 @@ export class AutoupdateService extends OpenSlidesComponent {
// Add the objects to the DataStore.
Object.keys(autoupdate.changed).forEach(collection => {
const targetClass = CollectionStringModelMapperService.getModelConstructor(collection);
const targetClass = this.modelMapper.getModelConstructor(collection);
if (!targetClass) {
// TODO: throw an error later..
/*throw new Error*/ console.log(`Unregistered resource ${collection}`);

View File

@ -1,8 +1,12 @@
import { Injectable } from '@angular/core';
import { ModelConstructor, BaseModel } from '../../shared/models/base/base-model';
/**
* Registeres the mapping of collection strings <--> actual types. Every Model should register itself here.
*/
@Injectable({
providedIn: 'root'
})
export class CollectionStringModelMapperService {
/**
* Mapps collection strings to model constructors. Accessed by {@method registerCollectionElement} and
@ -10,26 +14,10 @@ export class CollectionStringModelMapperService {
*/
private static collectionStringsTypeMapping: { [collectionString: string]: ModelConstructor<BaseModel> } = {};
/**
* Registers the type to the collection string
* @param collectionString
* @param type
*/
public static registerCollectionElement(collectionString: string, type: ModelConstructor<BaseModel>): void {
CollectionStringModelMapperService.collectionStringsTypeMapping[collectionString] = type;
}
/**
* Returns the constructor of the requested collection or undefined, if it is not registered.
* @param collectionString the requested collection
*/
public static getModelConstructor(collectionString: string): ModelConstructor<BaseModel> {
return CollectionStringModelMapperService.collectionStringsTypeMapping[collectionString];
}
/**
* Returns the collection string of a given ModelConstructor or undefined, if it is not registered.
* @param ctor
* @deprecated Should inject this service and don't use the static functions.
*/
public static getCollectionString(ctor: ModelConstructor<BaseModel>): string {
return Object.keys(CollectionStringModelMapperService.collectionStringsTypeMapping).find(
@ -44,4 +32,33 @@ export class CollectionStringModelMapperService {
* @param websocketService
*/
public constructor() {}
/**
* Registers the type to the collection string
* @param collectionString
* @param type
*/
public registerCollectionElement(collectionString: string, type: ModelConstructor<BaseModel>): void {
CollectionStringModelMapperService.collectionStringsTypeMapping[collectionString] = type;
}
/**
* Returns the constructor of the requested collection or undefined, if it is not registered.
* @param collectionString the requested collection
*/
public getModelConstructor(collectionString: string): ModelConstructor<BaseModel> {
return CollectionStringModelMapperService.collectionStringsTypeMapping[collectionString];
}
/**
* Returns the collection string of a given ModelConstructor or undefined, if it is not registered.
* @param ctor
*/
public getCollectionString(ctor: ModelConstructor<BaseModel>): string {
return Object.keys(CollectionStringModelMapperService.collectionStringsTypeMapping).find(
(collectionString: string) => {
return ctor === CollectionStringModelMapperService.collectionStringsTypeMapping[collectionString];
}
);
}
}

View File

@ -115,7 +115,7 @@ export class DataStoreService {
* Empty constructor for dataStore
* @param cacheService use CacheService to cache the DataStore.
*/
public constructor(private cacheService: CacheService) {
public constructor(private cacheService: CacheService, private modelMapper: CollectionStringModelMapperService) {
if (DataStoreService.wasInstantiated) {
throw new Error('The Datastore should just be instantiated once!');
}
@ -158,7 +158,7 @@ export class DataStoreService {
const storage: ModelStorage = {};
Object.keys(serializedStore).forEach(collectionString => {
storage[collectionString] = {} as ModelCollection;
const target = CollectionStringModelMapperService.getModelConstructor(collectionString);
const target = this.modelMapper.getModelConstructor(collectionString);
if (target) {
Object.keys(serializedStore[collectionString]).forEach(id => {
const data = JSON.parse(serializedStore[collectionString][id]);
@ -186,7 +186,7 @@ export class DataStoreService {
if (typeof collectionType === 'string') {
return collectionType;
} else {
return CollectionStringModelMapperService.getCollectionString(collectionType);
return this.modelMapper.getCollectionString(collectionType);
}
}

View File

@ -0,0 +1,15 @@
import { TestBed, inject } from '@angular/core/testing';
import { MainMenuService } from './main-menu.service';
describe('MainMenuService', () => {
beforeEach(() => {
TestBed.configureTestingModule({
providers: [MainMenuService]
});
});
it('should be created', inject([MainMenuService], (service: MainMenuService) => {
expect(service).toBeTruthy();
}));
});

View File

@ -0,0 +1,61 @@
import { Injectable } from '@angular/core';
/**
* This represents one entry in the main menu
*/
export interface MainMenuEntry {
/**
* The route for the router to navigate to on click.
*/
route: string;
/**
* The display string to be shown.
*/
displayName: string;
/**
* The font awesom icon to display.
*/
icon: string;
/**
* For sorting the entries.
*/
weight: number;
/**
* The permission to see the entry.
*/
permission: string;
}
/**
* Collects main menu entries and provides them to the main menu component.
*/
@Injectable({
providedIn: 'root'
})
export class MainMenuService {
/**
* A list of sorted entries.
*/
private _entries: MainMenuEntry[] = [];
/**
* Make the entries public.
*/
public get entries(): MainMenuEntry[] {
return this._entries;
}
public constructor() {}
/**
* Adds entries to the mainmenu.
* @param entries The entries to add
*/
public registerEntries(entries: MainMenuEntry[]): void {
this._entries.push(...entries);
this._entries = this._entries.sort((a, b) => a.weight - b.weight);
}
}

View File

@ -56,5 +56,3 @@ export class Item extends ProjectableBaseModel {
return this.getListTitle();
}
}
ProjectableBaseModel.registerCollectionElement('agenda/item', Item);

View File

@ -56,5 +56,3 @@ export class Assignment extends AgendaBaseModel {
return 'TODO';
}
}
AgendaBaseModel.registerCollectionElement('assignments/assignment', Assignment);

View File

@ -1,6 +1,5 @@
import { OpenSlidesComponent } from 'app/openslides.component';
import { Deserializable } from './deserializable';
import { CollectionStringModelMapperService } from '../../../core/services/collectionStringModelMapper.service';
import { Displayable } from './displayable';
import { Identifiable } from './identifiable';
@ -14,15 +13,6 @@ export interface ModelConstructor<T extends BaseModel<T>> {
*/
export abstract class BaseModel<T = object> extends OpenSlidesComponent
implements Deserializable, Displayable, Identifiable {
/**
* Register the collection string to the type.
* @param collectionString
* @param type
*/
public static registerCollectionElement(collectionString: string, type: any): void {
CollectionStringModelMapperService.registerCollectionElement(collectionString, type);
}
/**
* force children of BaseModel to have a collectionString.
*

View File

@ -18,5 +18,3 @@ export class ChatMessage extends BaseModel<ChatMessage> {
return 'Chatmessage';
}
}
BaseModel.registerCollectionElement('core/chat-message', ChatMessage);

View File

@ -17,5 +17,3 @@ export class Config extends BaseModel {
return this.key;
}
}
BaseModel.registerCollectionElement('core/config', Config);

View File

@ -19,5 +19,3 @@ export class Countdown extends ProjectableBaseModel {
return this.description;
}
}
ProjectableBaseModel.registerCollectionElement('core/countdown', Countdown);

View File

@ -16,5 +16,3 @@ export class ProjectorMessage extends ProjectableBaseModel {
return 'Projectormessage';
}
}
ProjectableBaseModel.registerCollectionElement('core/projector-message', ProjectorMessage);

View File

@ -23,5 +23,3 @@ export class Projector extends BaseModel<Projector> {
return this.name;
}
}
BaseModel.registerCollectionElement('core/projector', Projector);

View File

@ -16,5 +16,3 @@ export class Tag extends BaseModel<Tag> {
return this.name;
}
}
BaseModel.registerCollectionElement('core/tag', Tag);

View File

@ -28,5 +28,3 @@ export class Mediafile extends ProjectableBaseModel {
return this.title;
}
}
ProjectableBaseModel.registerCollectionElement('mediafiles/mediafile', Mediafile);

View File

@ -17,5 +17,3 @@ export class Category extends BaseModel<Category> {
return this.prefix + ' - ' + this.name;
}
}
BaseModel.registerCollectionElement('motions/category', Category);

View File

@ -21,5 +21,3 @@ export class MotionBlock extends AgendaBaseModel {
return 'TODO';
}
}
AgendaBaseModel.registerCollectionElement('motions/motion-block', MotionBlock);

View File

@ -23,5 +23,3 @@ export class MotionChangeReco extends BaseModel<MotionChangeReco> {
return 'Changerecommendation';
}
}
BaseModel.registerCollectionElement('motions/motion-change-recommendation', MotionChangeReco);

View File

@ -18,5 +18,3 @@ export class MotionCommentSection extends BaseModel<MotionCommentSection> {
return this.name;
}
}
BaseModel.registerCollectionElement('motions/motion-comment-section', MotionCommentSection);

View File

@ -1,8 +1,6 @@
import { MotionSubmitter } from './motion-submitter';
import { MotionLog } from './motion-log';
import { Category } from './category';
import { MotionComment } from './motion-comment';
import { Workflow } from './workflow';
import { AgendaBaseModel } from '../base/agenda-base-model';
/**
@ -99,10 +97,3 @@ export class Motion extends AgendaBaseModel {
}
}
}
/**
* Hack to get them loaded at last
*/
AgendaBaseModel.registerCollectionElement('motions/motion', Motion);
AgendaBaseModel.registerCollectionElement('motions/category', Category);
AgendaBaseModel.registerCollectionElement('motions/workflow', Workflow);

View File

@ -58,5 +58,3 @@ export class Workflow extends BaseModel<Workflow> {
return this.name;
}
}
BaseModel.registerCollectionElement('motions/workflow', Workflow);

View File

@ -28,5 +28,3 @@ export class Topic extends AgendaBaseModel {
return 'TODO';
}
}
AgendaBaseModel.registerCollectionElement('topics/topic', Topic);

View File

@ -17,5 +17,3 @@ export class Group extends BaseModel<Group> {
return this.name;
}
}
BaseModel.registerCollectionElement('users/group', Group);

View File

@ -17,5 +17,3 @@ export class PersonalNote extends BaseModel<PersonalNote> {
return 'Personal note';
}
}
BaseModel.registerCollectionElement('users/personal-note', PersonalNote);

View File

@ -89,5 +89,3 @@ export class User extends ProjectableBaseModel {
return this.short_name;
}
}
ProjectableBaseModel.registerCollectionElement('users/user', User);

View File

@ -0,0 +1,17 @@
import { AppConfig } from '../base/app-config';
import { Item } from '../../shared/models/agenda/item';
import { Topic } from '../../shared/models/topics/topic';
export const AgendaAppConfig: AppConfig = {
name: 'agenda',
models: [{ collectionString: 'agenda/item', model: Item }, { collectionString: 'topics/topic', model: Topic }],
mainMenuEntries: [
{
route: '/agenda',
displayName: 'Agenda',
icon: 'calendar',
weight: 200,
permission: 'agenda.can_see'
}
]
};

View File

@ -0,0 +1,16 @@
import { AppConfig } from '../base/app-config';
import { Assignment } from '../../shared/models/assignments/assignment';
export const AssignmentsAppConfig: AppConfig = {
name: 'assignments',
models: [{ collectionString: 'assignments/assignment', model: Assignment }],
mainMenuEntries: [
{
route: '/assignments',
displayName: 'Elections',
icon: 'chart-pie',
weight: 400,
permission: 'assignments.can_see'
}
]
};

View File

@ -0,0 +1,25 @@
import { ModelConstructor, BaseModel } from '../../shared/models/base/base-model';
import { MainMenuEntry } from '../../core/services/main-menu.service';
/**
* The configuration of an app.
*/
export interface AppConfig {
/**
* The name.
*/
name: string;
/**
* All models, that should be registered.
*/
models?: {
collectionString: string;
model: ModelConstructor<BaseModel>;
}[];
/**
* Main menu entries.
*/
mainMenuEntries?: MainMenuEntry[];
}

View File

@ -0,0 +1,26 @@
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { PrivacyPolicyComponent } from './components/privacy-policy/privacy-policy.component';
import { StartComponent } from './components/start/start.component';
import { LegalNoticeComponent } from './components/legal-notice/legal-notice.component';
const routes: Routes = [
{
path: '',
component: StartComponent
},
{
path: 'legalnotice',
component: LegalNoticeComponent
},
{
path: 'privacypolicy',
component: PrivacyPolicyComponent
}
];
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule]
})
export class CommonRoutingModule {}

View File

@ -0,0 +1,26 @@
import { AppConfig } from '../base/app-config';
import { Projector } from '../../shared/models/core/projector';
import { Countdown } from '../../shared/models/core/countdown';
import { ChatMessage } from '../../shared/models/core/chat-message';
import { ProjectorMessage } from '../../shared/models/core/projector-message';
import { Tag } from '../../shared/models/core/tag';
export const CommonAppConfig: AppConfig = {
name: 'common',
models: [
{ collectionString: 'core/projector', model: Projector },
{ collectionString: 'core/chat-message', model: ChatMessage },
{ collectionString: 'core/countdown', model: Countdown },
{ collectionString: 'core/projector-message', model: ProjectorMessage },
{ collectionString: 'core/tag', model: Tag }
],
mainMenuEntries: [
{
route: '/',
displayName: 'Home',
icon: 'home',
weight: 100,
permission: 'core.can_see_frontpage'
}
]
};

View File

@ -0,0 +1,13 @@
import { CommonModule } from './common.module';
describe('CommonModule', () => {
let commonModule: CommonModule;
beforeEach(() => {
commonModule = new CommonModule();
});
it('should create an instance', () => {
expect(commonModule).toBeTruthy();
});
});

View File

@ -0,0 +1,14 @@
import { NgModule } from '@angular/core';
import { CommonModule as AngularCommonModule } from '@angular/common';
import { CommonRoutingModule } from './common-routing.module';
import { SharedModule } from '../../shared/shared.module';
import { PrivacyPolicyComponent } from './components/privacy-policy/privacy-policy.component';
import { StartComponent } from './components/start/start.component';
import { LegalNoticeComponent } from './components/legal-notice/legal-notice.component';
@NgModule({
imports: [AngularCommonModule, CommonRoutingModule, SharedModule],
declarations: [PrivacyPolicyComponent, StartComponent, LegalNoticeComponent]
})
export class CommonModule {}

View File

@ -7,8 +7,6 @@
<h4> {{welcomeTitle | translate}} </h4>
<span> {{welcomeText | translate}} </span>
<button mat-button (click)="DataStoreTest()">DataStoreTest</button>
<br/>
<button mat-button (click)="TranslateTest()">Translate in console</button>
<br/>
<button mat-button (click)="giveDataStore()">print the dataStore</button>

View File

@ -5,11 +5,10 @@ import { BaseComponent } from 'app/base.component';
import { TranslateService } from '@ngx-translate/core'; // showcase
// for testing the DS and BaseModel
import { User } from 'app/shared/models/users/user';
import { Config } from '../../shared/models/core/config';
import { Motion } from '../../shared/models/motions/motion';
import { MotionSubmitter } from '../../shared/models/motions/motion-submitter';
import { DataStoreService } from '../../core/services/data-store.service';
import { Config } from '../../../../shared/models/core/config';
import { Motion } from '../../../../shared/models/motions/motion';
import { MotionSubmitter } from '../../../../shared/models/motions/motion-submitter';
import { DataStoreService } from '../../../../core/services/data-store.service';
@Component({
selector: 'os-start',
@ -74,36 +73,6 @@ export class StartComponent extends BaseComponent implements OnInit {
});
}
/**
* test data store
*/
public DataStoreTest(): void {
console.log('add a user to dataStore');
this.DS.add(new User({ id: 100 }));
console.log('add three users to dataStore');
this.DS.add(new User({ id: 200 }), new User({ id: 201 }), new User({ id: 202 }));
console.log('use the spread operator "..." to add an array');
const userArray = [];
for (let i = 300; i < 400; i++) {
userArray.push(new User({ id: i }));
}
this.DS.add(...userArray);
console.log('try to get user with ID 1:');
const user1fromStore = this.DS.get<User>(User, 1);
console.log('the user: ', user1fromStore);
console.log('remove a single user:');
this.DS.remove('users/user', 100);
console.log('remove more users');
this.DS.remove('users/user', 200, 201, 202);
console.log('remove an array of users');
this.DS.remove('users/user', ...[321, 363, 399]);
console.log('test filter: ');
console.log(this.DS.filter<User>(User, user => user.id === 1));
}
/**
* function to print datastore
*/

View File

@ -0,0 +1,16 @@
import { AppConfig } from '../base/app-config';
import { Mediafile } from '../../shared/models/mediafiles/mediafile';
export const MediafileAppConfig: AppConfig = {
name: 'mediafiles',
models: [{ collectionString: 'mediafiles/mediafile', model: Mediafile }],
mainMenuEntries: [
{
route: '/mediafiles',
displayName: 'Files',
icon: 'paperclip',
weight: 600,
permission: 'mediafiles.can_see'
}
]
};

View File

@ -0,0 +1,28 @@
import { AppConfig } from '../base/app-config';
import { Motion } from '../../shared/models/motions/motion';
import { Category } from '../../shared/models/motions/category';
import { Workflow } from '../../shared/models/motions/workflow';
import { MotionCommentSection } from '../../shared/models/motions/motion-comment-section';
import { MotionChangeReco } from '../../shared/models/motions/motion-change-reco';
import { MotionBlock } from '../../shared/models/motions/motion-block';
export const MotionsAppConfig: AppConfig = {
name: 'motions',
models: [
{ collectionString: 'motions/motion', model: Motion },
{ collectionString: 'motions/category', model: Category },
{ collectionString: 'motions/workflow', model: Workflow },
{ collectionString: 'motions/motion-comment-section', model: MotionCommentSection },
{ collectionString: 'motions/motion-change-recommendation', model: MotionChangeReco },
{ collectionString: 'motions/motion-block', model: MotionBlock }
],
mainMenuEntries: [
{
route: '/motions',
displayName: 'Motions',
icon: 'file-alt',
weight: 300,
permission: 'motions.can_see'
}
]
};

View File

@ -0,0 +1,16 @@
import { AppConfig } from '../base/app-config';
import { Config } from '../../shared/models/core/config';
export const SettingsAppConfig: AppConfig = {
name: 'settings',
models: [{ collectionString: 'core/config', model: Config }],
mainMenuEntries: [
{
route: '/settings',
displayName: 'Settings',
icon: 'cog',
weight: 700,
permission: 'core.can_manage_config'
}
]
};

View File

@ -3,9 +3,6 @@ import { Routes, RouterModule } from '@angular/router';
import { SiteComponent } from './site.component';
import { StartComponent } from './start/start.component';
import { PrivacyPolicyComponent } from './privacy-policy/privacy-policy.component';
import { LegalNoticeComponent } from './legal-notice/legal-notice.component';
import { AuthGuard } from '../core/services/auth-guard.service';
/**
@ -20,15 +17,7 @@ const routes: Routes = [
children: [
{
path: '',
component: StartComponent
},
{
path: 'legalnotice',
component: LegalNoticeComponent
},
{
path: 'privacypolicy',
component: PrivacyPolicyComponent
loadChildren: './common/common.module#CommonModule'
},
{
path: 'agenda',

View File

@ -45,35 +45,13 @@
<!-- navigation -->
<mat-nav-list class='main-nav'>
<a [@navItemAnim] *osPerms="'core.can_see_frontpage'" mat-list-item routerLink='/' routerLinkActive='active' [routerLinkActiveOptions]="{exact: true}"
(click)='toggleSideNav()'>
<fa-icon icon='home'></fa-icon>
<span translate>Home</span>
</a>
<a [@navItemAnim] *osPerms="'agenda.can_see'" mat-list-item routerLink='/agenda' routerLinkActive='active' (click)='toggleSideNav()'>
<fa-icon icon='calendar'></fa-icon>
<span translate>Agenda</span>
</a>
<a [@navItemAnim] *osPerms="'motions.can_see'" mat-list-item routerLink='/motions' routerLinkActive='active' (click)='toggleSideNav()'>
<fa-icon icon='file-alt'></fa-icon>
<span translate>Motions</span>
</a>
<a [@navItemAnim] *osPerms="'assignments.can_see'" mat-list-item routerLink='/assignments' routerLinkActive='active' (click)='vp.isMobile ? sideNav.toggle() : null'>
<fa-icon icon='chart-pie'></fa-icon>
<span translate>Assignments</span>
</a>
<a [@navItemAnim] *osPerms="'users.can_see_name'" mat-list-item routerLink='/users' routerLinkActive='active' (click)='toggleSideNav()'>
<fa-icon icon='user'></fa-icon>
<span translate>Participants</span>
</a>
<a [@navItemAnim] *osPerms="'mediafiles.can_see'" mat-list-item routerLink='/mediafiles' routerLinkActive='active' (click)='toggleSideNav()'>
<fa-icon icon='paperclip'></fa-icon>
<span translate>Files</span>
</a>
<a [@navItemAnim] *osPerms="'core.can_manage_config'" mat-list-item routerLink='/settings' routerLinkActive='active' (click)='toggleSideNav()'>
<fa-icon icon='cog'></fa-icon>
<span translate>Settings</span>
</a>
<span *ngFor="let entry of mainMenuService.entries">
<a [@navItemAnim] *osPerms="entry.permission" mat-list-item (click)='toggleSideNav()'
[routerLink]='entry.route' routerLinkActive='active' [routerLinkActiveOptions]="{exact: true}">
<fa-icon [icon]='entry.icon'></fa-icon>
{{ entry.displayName | translate}}
</a>
</span>
<mat-divider></mat-divider>
<a [@navItemAnim] *osPerms="'core.can_see_projector'" mat-list-item routerLink='/projector' routerLinkActive='active' (click)='toggleSideNav()'>
<fa-icon icon='video'></fa-icon>

View File

@ -9,8 +9,7 @@ import { BaseComponent } from 'app/base.component';
import { pageTransition, navItemAnim } from 'app/shared/animations';
import { MatDialog, MatSidenav } from '@angular/material';
import { ViewportService } from '../core/services/viewport.service';
import { Projector } from '../shared/models/core/projector';
import { Tag } from '../shared/models/core/tag';
import { MainMenuService } from '../core/services/main-menu.service';
@Component({
selector: 'os-site',
@ -51,7 +50,8 @@ export class SiteComponent extends BaseComponent implements OnInit {
public operator: OperatorService,
public vp: ViewportService,
public translate: TranslateService,
public dialog: MatDialog
public dialog: MatDialog,
public mainMenuService: MainMenuService // used in the component
) {
super();
@ -75,11 +75,6 @@ export class SiteComponent extends BaseComponent implements OnInit {
// this.translate.get('Motions').subscribe((res: string) => {
// console.log('translation of motions in the target language: ' + res);
// });
// tslint:disable-next-line
const p: Projector = new Projector(); // Needed, that the Projector.ts is loaded. Can be removed, if something else creates/uses projectors.
// tslint:disable-next-line
const t: Tag = new Tag(); // Needed, that the Tag.ts is loaded. Can be removed, if something else creates/uses tags.
}
/**

View File

@ -1,16 +1,13 @@
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { SiteRoutingModule } from './site-routing.module';
import { SharedModule } from 'app/shared/shared.module';
import { SiteComponent } from './site.component';
import { StartComponent } from './start/start.component';
import { LegalNoticeComponent } from './legal-notice/legal-notice.component';
import { PrivacyPolicyComponent } from './privacy-policy/privacy-policy.component';
import { SiteRoutingModule } from './site-routing.module';
@NgModule({
imports: [CommonModule, SharedModule, SiteRoutingModule],
declarations: [SiteComponent, StartComponent, LegalNoticeComponent, PrivacyPolicyComponent]
declarations: [SiteComponent]
})
export class SiteModule {}

View File

@ -0,0 +1,22 @@
import { AppConfig } from '../base/app-config';
import { User } from '../../shared/models/users/user';
import { Group } from '../../shared/models/users/group';
import { PersonalNote } from '../../shared/models/users/personal-note';
export const UsersAppConfig: AppConfig = {
name: 'users',
models: [
{ collectionString: 'users/user', model: User },
{ collectionString: 'users/group', model: Group },
{ collectionString: 'users/personal-note', model: PersonalNote }
],
mainMenuEntries: [
{
route: '/users',
displayName: 'Participants',
icon: 'user',
weight: 500,
permission: 'users.can_see_name'
}
]
};

1
client/src/plugins.ts Normal file
View File

@ -0,0 +1 @@
export const plugins: string[] = [];