Merge pull request #3890 from FinnStutzenstein/plugins3

App initialization
This commit is contained in:
Finn Stutzenstein 2018-09-28 15:55:37 +02:00 committed by GitHub
commit 19d78a3361
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
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[] = [];