Merge pull request #5811 from tsiegleauq/applause-client
Add applause in client
This commit is contained in:
commit
cc65b756c7
2437
client/package-lock.json
generated
2437
client/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -66,6 +66,7 @@
|
||||
"lz4js": "^0.2.0",
|
||||
"material-icon-font": "git+https://github.com/petergng/materialIconFont.git",
|
||||
"moment": "^2.27.0",
|
||||
"ng-particles": "^2.1.11",
|
||||
"ng2-charts": "^2.4.0",
|
||||
"ng2-pdf-viewer": "6.3.2",
|
||||
"ngx-device-detector": "^2.0.0",
|
||||
@ -78,6 +79,7 @@
|
||||
"rxjs": "^6.6.2",
|
||||
"tinymce": "5.4.2",
|
||||
"tslib": "^1.10.0",
|
||||
"tsparticles": "^1.18.11",
|
||||
"video.js": "^7.8.4",
|
||||
"zone.js": "~0.10.2"
|
||||
},
|
||||
|
@ -1,4 +1,6 @@
|
||||
import { ApplicationRef, Component } from '@angular/core';
|
||||
import { MatIconRegistry } from '@angular/material/icon';
|
||||
import { DomSanitizer } from '@angular/platform-browser';
|
||||
import { Router } from '@angular/router';
|
||||
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
@ -70,6 +72,8 @@ export class AppComponent {
|
||||
* @param dataStoreUpgradeService
|
||||
*/
|
||||
public constructor(
|
||||
private matIconRegistry: MatIconRegistry,
|
||||
private domSanitizer: DomSanitizer,
|
||||
translate: TranslateService,
|
||||
appRef: ApplicationRef,
|
||||
servertimeService: ServertimeService,
|
||||
@ -100,6 +104,7 @@ export class AppComponent {
|
||||
this.overloadArrayFunctions();
|
||||
this.overloadSetFunctions();
|
||||
this.overloadModulo();
|
||||
this.loadCustomIcons();
|
||||
|
||||
// Wait until the App reaches a stable state.
|
||||
// Required for the Service Worker.
|
||||
@ -204,4 +209,11 @@ export class AppComponent {
|
||||
enumerable: false
|
||||
});
|
||||
}
|
||||
|
||||
private loadCustomIcons(): void {
|
||||
this.matIconRegistry.addSvgIcon(
|
||||
`clapping_hands`,
|
||||
this.domSanitizer.bypassSecurityTrustResourceUrl('../assets/svg/clapping_hands.svg')
|
||||
);
|
||||
}
|
||||
}
|
||||
|
20
client/src/app/core/ui-services/applause.service.spec.ts
Normal file
20
client/src/app/core/ui-services/applause.service.spec.ts
Normal file
@ -0,0 +1,20 @@
|
||||
import { TestBed } from '@angular/core/testing';
|
||||
|
||||
import { E2EImportsModule } from 'e2e-imports.module';
|
||||
|
||||
import { ApplauseService } from './applause.service';
|
||||
|
||||
describe('ApplauseService', () => {
|
||||
let service: ApplauseService;
|
||||
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [E2EImportsModule]
|
||||
});
|
||||
service = TestBed.inject(ApplauseService);
|
||||
});
|
||||
|
||||
it('should be created', () => {
|
||||
expect(service).toBeTruthy();
|
||||
});
|
||||
});
|
84
client/src/app/core/ui-services/applause.service.ts
Normal file
84
client/src/app/core/ui-services/applause.service.ts
Normal file
@ -0,0 +1,84 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
|
||||
import { Observable, Subject } from 'rxjs';
|
||||
import { distinctUntilChanged, filter, map } from 'rxjs/operators';
|
||||
|
||||
import { ConfigService } from './config.service';
|
||||
import { HttpService } from '../core-services/http.service';
|
||||
import { NotifyService } from '../core-services/notify.service';
|
||||
|
||||
export interface Applause {
|
||||
level: number;
|
||||
presentUsers: number;
|
||||
}
|
||||
|
||||
export enum ApplauseType {
|
||||
particles = 'applause-type-particles',
|
||||
bar = 'applause-type-bar'
|
||||
}
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class ApplauseService {
|
||||
private applausePath = '/system/applause';
|
||||
private applauseNotifyPath = 'applause';
|
||||
private minApplauseLevel: number;
|
||||
private maxApplauseLevel: number;
|
||||
private presentApplauseUsers: number;
|
||||
|
||||
public applauseType: ApplauseType;
|
||||
|
||||
private applauseLevelSubject: Subject<number> = new Subject<number>();
|
||||
public applauseLevelObservable = this.applauseLevelSubject.asObservable();
|
||||
|
||||
private get maxApplause(): number {
|
||||
return this.maxApplauseLevel || this.presentApplauseUsers || 0;
|
||||
}
|
||||
|
||||
public constructor(
|
||||
configService: ConfigService,
|
||||
private httpService: HttpService,
|
||||
private notifyService: NotifyService
|
||||
) {
|
||||
configService.get<number>('general_system_applause_min_amount').subscribe(minLevel => {
|
||||
this.minApplauseLevel = minLevel;
|
||||
});
|
||||
configService.get<number>('general_system_applause_max_amount').subscribe(maxLevel => {
|
||||
this.maxApplauseLevel = maxLevel;
|
||||
});
|
||||
configService.get<ApplauseType>('general_system_applause_type').subscribe((type: ApplauseType) => {
|
||||
this.applauseType = type;
|
||||
});
|
||||
this.notifyService
|
||||
.getMessageObservable<Applause>(this.applauseNotifyPath)
|
||||
.pipe(
|
||||
map(notify => notify.message as Applause),
|
||||
/**
|
||||
* only updates when the effective applause level changes
|
||||
*/
|
||||
distinctUntilChanged((prev, curr) => {
|
||||
return prev.level === curr.level;
|
||||
}),
|
||||
filter(curr => {
|
||||
return curr.level === 0 || curr.level >= this.minApplauseLevel;
|
||||
})
|
||||
)
|
||||
.subscribe(applause => {
|
||||
this.presentApplauseUsers = applause.presentUsers;
|
||||
this.applauseLevelSubject.next(applause.level);
|
||||
});
|
||||
}
|
||||
|
||||
public async sendApplause(): Promise<void> {
|
||||
await this.httpService.post(this.applausePath);
|
||||
}
|
||||
|
||||
public getApplauseQuote(applauseLevel: number): number {
|
||||
if (!applauseLevel) {
|
||||
return 0;
|
||||
}
|
||||
const quote = applauseLevel / this.maxApplause || 0;
|
||||
return quote > 1 ? 1 : quote;
|
||||
}
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
import { animate, style, transition, trigger } from '@angular/animations';
|
||||
import { animate, state, style, transition, trigger } from '@angular/animations';
|
||||
|
||||
const slideIn = [style({ transform: 'translateX(-85%)' }), animate('600ms ease')];
|
||||
const slideOut = [
|
||||
@ -11,4 +11,9 @@ const slideOut = [
|
||||
)
|
||||
];
|
||||
|
||||
export const fadeAnimation = trigger('fade', [
|
||||
state('in', style({ opacity: 1 })),
|
||||
transition(':enter', [style({ opacity: 0 }), animate(600)]),
|
||||
transition(':leave', animate(600, style({ opacity: 0 })))
|
||||
]);
|
||||
export const navItemAnim = trigger('navItemAnim', [transition(':enter', slideIn), transition(':leave', slideOut)]);
|
||||
|
@ -0,0 +1,9 @@
|
||||
<div class="bar-wrapper" *ngIf="isApplauseTypeBar">
|
||||
<os-progress class="progress-bar" [value]="percent">
|
||||
<div class="level-indicator">
|
||||
<div class="level">
|
||||
<b *ngIf="showLevel && hasLevel" [@fade]="'in'">{{ level }}</b>
|
||||
</div>
|
||||
</div>
|
||||
</os-progress>
|
||||
</div>
|
@ -0,0 +1,26 @@
|
||||
.bar-wrapper {
|
||||
min-width: 30px;
|
||||
height: 100%;
|
||||
|
||||
.progress-bar {
|
||||
display: block;
|
||||
width: 30px;
|
||||
height: 100%;
|
||||
margin-left: auto;
|
||||
}
|
||||
}
|
||||
|
||||
.level-indicator {
|
||||
height: 50px;
|
||||
display: block;
|
||||
text-align: center;
|
||||
|
||||
.level {
|
||||
display: inline-block;
|
||||
margin-top: 25px;
|
||||
}
|
||||
}
|
||||
|
||||
.particle-display {
|
||||
// height: 100%;
|
||||
}
|
@ -0,0 +1,26 @@
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { E2EImportsModule } from 'e2e-imports.module';
|
||||
|
||||
import { ApplauseDisplayComponent } from './applause-display.component';
|
||||
|
||||
describe('ApplauseDisplayComponent', () => {
|
||||
let component: ApplauseDisplayComponent;
|
||||
let fixture: ComponentFixture<ApplauseDisplayComponent>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [E2EImportsModule]
|
||||
}).compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(ApplauseDisplayComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
@ -0,0 +1,56 @@
|
||||
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, OnInit, ViewEncapsulation } from '@angular/core';
|
||||
import { MatSnackBar } from '@angular/material/snack-bar';
|
||||
import { Title } from '@angular/platform-browser';
|
||||
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
|
||||
import { Applause, ApplauseService, ApplauseType } from 'app/core/ui-services/applause.service';
|
||||
import { ConfigService } from 'app/core/ui-services/config.service';
|
||||
import { fadeAnimation } from 'app/shared/animations';
|
||||
import { BaseViewComponentDirective } from 'app/site/base/base-view';
|
||||
|
||||
@Component({
|
||||
selector: 'os-applause-display',
|
||||
templateUrl: './applause-display.component.html',
|
||||
styleUrls: ['./applause-display.component.scss'],
|
||||
animations: [fadeAnimation],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
encapsulation: ViewEncapsulation.None
|
||||
})
|
||||
export class ApplauseDisplayComponent extends BaseViewComponentDirective {
|
||||
public level = 0;
|
||||
public showLevel: boolean;
|
||||
public percent = 0;
|
||||
|
||||
public get hasLevel(): boolean {
|
||||
return !!this.level;
|
||||
}
|
||||
|
||||
public get isApplauseTypeBar(): boolean {
|
||||
return this.applauseService.applauseType === ApplauseType.bar;
|
||||
}
|
||||
|
||||
public constructor(
|
||||
title: Title,
|
||||
translate: TranslateService,
|
||||
matSnackBar: MatSnackBar,
|
||||
cd: ChangeDetectorRef,
|
||||
private applauseService: ApplauseService,
|
||||
configService: ConfigService
|
||||
) {
|
||||
super(title, translate, matSnackBar);
|
||||
this.subscriptions.push(
|
||||
applauseService.applauseLevelObservable.subscribe(applauseLevel => {
|
||||
this.level = applauseLevel || 0;
|
||||
this.percent = this.applauseService.getApplauseQuote(this.level) * 100;
|
||||
cd.markForCheck();
|
||||
}),
|
||||
configService.get<ApplauseType>('general_system_applause_type').subscribe(() => {
|
||||
cd.markForCheck();
|
||||
}),
|
||||
configService.get<boolean>('general_system_applause_show_level').subscribe(show => {
|
||||
this.showLevel = show;
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
@ -61,9 +61,7 @@
|
||||
matTooltip="{{ 'Exit live conference and continue livestream' | translate }}"
|
||||
*ngIf="videoStreamUrl && canSeeLiveStream && !isJitsiDialogOpen"
|
||||
>
|
||||
<mat-icon color="warn">
|
||||
meeting_room
|
||||
</mat-icon>
|
||||
<mat-icon color="warn"> meeting_room </mat-icon>
|
||||
</button>
|
||||
|
||||
<!-- mute/unmute button -->
|
||||
@ -90,7 +88,11 @@
|
||||
(click)="enterConversation()"
|
||||
matTooltip="{{ 'Enter live conference' | translate }}"
|
||||
>
|
||||
<mat-icon color="primary" [@fadeInOut]="isEnterMeetingRoomVisible" (@fadeInOut.done)="triggerMeetingRoomButtonAnimation()">
|
||||
<mat-icon
|
||||
color="primary"
|
||||
[@fadeInOut]="isEnterMeetingRoomVisible"
|
||||
(@fadeInOut.done)="triggerMeetingRoomButtonAnimation()"
|
||||
>
|
||||
meeting_room
|
||||
</mat-icon>
|
||||
</button>
|
||||
@ -103,11 +105,23 @@
|
||||
*ngIf="enableJitsi && !isAccessPermitted"
|
||||
[routerLink]="['/agenda/speakers']"
|
||||
>
|
||||
<mat-icon>
|
||||
no_meeting_room
|
||||
</mat-icon>
|
||||
<mat-icon> no_meeting_room </mat-icon>
|
||||
</a>
|
||||
</ng-container>
|
||||
|
||||
<!-- applause button -->
|
||||
<button
|
||||
class="quick-icon indicator"
|
||||
[disabled]="applauseDisabled"
|
||||
mat-mini-fab
|
||||
(click)="sendApplause()"
|
||||
matTooltip="{{ 'Send applause' | translate }}"
|
||||
*ngIf="showApplause"
|
||||
[matBadge]="showApplauseBadge ? applauseLevel : null"
|
||||
matBadgeColor="accent"
|
||||
>
|
||||
<mat-icon svgIcon="clapping_hands"></mat-icon>
|
||||
</button>
|
||||
</span>
|
||||
|
||||
<span
|
||||
@ -118,6 +132,8 @@
|
||||
'cast-shadow': showJitsiWindow
|
||||
}"
|
||||
>
|
||||
<os-applause-display *ngIf="showApplause && isApplauseTypeBar" class="applause"></os-applause-display>
|
||||
|
||||
<!-- open-window button -->
|
||||
<button class="toggle-list-button" mat-button (click)="toggleShowJitsi()">
|
||||
<ng-container *ngIf="currentState == state.jitsi">
|
||||
@ -149,7 +165,7 @@
|
||||
<div
|
||||
class="jitsi-list"
|
||||
[ngClass]="{
|
||||
'cdk-visually-hidden': !showJitsiWindow
|
||||
'hide-height': !showJitsiWindow
|
||||
}"
|
||||
>
|
||||
<ng-container *ngIf="currentState == state.jitsi">
|
||||
@ -175,6 +191,10 @@
|
||||
|
||||
<!-- user list -->
|
||||
<div class="room-members" *ngIf="isJitsiActive && isJoined">
|
||||
<os-particle-display
|
||||
*ngIf="isApplauseTypeParticles"
|
||||
class="room-list-applause-particles"
|
||||
></os-particle-display>
|
||||
<div class="member-list">
|
||||
<ol>
|
||||
<li
|
||||
@ -196,6 +216,7 @@
|
||||
<ng-container *ngIf="currentState == state.stream">
|
||||
<os-vjs-player
|
||||
[videoUrl]="videoStreamUrl"
|
||||
[showParticles]="isApplauseTypeParticles"
|
||||
(started)="onSteamStarted()"
|
||||
*ngIf="(canSeeLiveStream && !streamActiveInAnotherTab) || streamRunning"
|
||||
></os-vjs-player>
|
||||
|
@ -56,6 +56,7 @@
|
||||
|
||||
.jitsi-bar {
|
||||
display: flex;
|
||||
position: relative;
|
||||
justify-content: flex-end;
|
||||
$wrapper-padding: 5px;
|
||||
$bar-height: 40px;
|
||||
@ -90,11 +91,20 @@
|
||||
}
|
||||
|
||||
.list-wrapper {
|
||||
position: relative;
|
||||
pointer-events: all;
|
||||
min-height: $bar-height;
|
||||
padding-top: $wrapper-padding;
|
||||
border-top-right-radius: 4px;
|
||||
|
||||
.applause {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
width: 90px;
|
||||
left: -90px;
|
||||
bottom: 50px;
|
||||
}
|
||||
|
||||
.toggle-list-button {
|
||||
position: relative;
|
||||
line-height: normal;
|
||||
@ -121,6 +131,7 @@
|
||||
.jitsi-list {
|
||||
.content {
|
||||
height: 40vh;
|
||||
max-height: 100%;
|
||||
clear: both;
|
||||
|
||||
.disconnected {
|
||||
@ -136,7 +147,15 @@
|
||||
}
|
||||
|
||||
.room-members {
|
||||
height: inherit;
|
||||
height: 100%;
|
||||
position: relative;
|
||||
|
||||
.room-list-applause-particles {
|
||||
position: absolute;
|
||||
height: 100%;
|
||||
width: 70px;
|
||||
right: 0;
|
||||
}
|
||||
|
||||
.member-list {
|
||||
max-height: 100%;
|
||||
|
@ -17,6 +17,10 @@
|
||||
|
||||
.indicator {
|
||||
color: mat-color($primary, default-contrast);
|
||||
|
||||
svg path {
|
||||
fill: mat-color($primary) !important;
|
||||
}
|
||||
}
|
||||
|
||||
.toggle-list-button {
|
||||
|
@ -11,6 +11,7 @@ import { ConstantsService } from 'app/core/core-services/constants.service';
|
||||
import { OperatorService } from 'app/core/core-services/operator.service';
|
||||
import { Deferred } from 'app/core/promises/deferred';
|
||||
import { UserRepositoryService } from 'app/core/repositories/users/user-repository.service';
|
||||
import { ApplauseService, ApplauseType } from 'app/core/ui-services/applause.service';
|
||||
import { ConfigService } from 'app/core/ui-services/config.service';
|
||||
import { UserMediaPermService } from 'app/core/ui-services/user-media-perm.service';
|
||||
import { UserListIndexType } from 'app/site/agenda/models/view-list-of-speakers';
|
||||
@ -96,6 +97,10 @@ export class JitsiComponent extends BaseViewComponentDirective implements OnInit
|
||||
public showJitsiWindow = true;
|
||||
public muted = true;
|
||||
|
||||
public showApplause: boolean;
|
||||
public applauseDisabled = false;
|
||||
private applauseTimeout: number;
|
||||
|
||||
@ViewChild('jitsi')
|
||||
private jitsiNode: ElementRef;
|
||||
|
||||
@ -180,6 +185,26 @@ export class JitsiComponent extends BaseViewComponentDirective implements OnInit
|
||||
public currentState: ConferenceState;
|
||||
public isEnterMeetingRoomVisible = true;
|
||||
|
||||
public applauseLevel = 0;
|
||||
private showApplauseLevel: boolean;
|
||||
private isApplausBarUsed: boolean;
|
||||
|
||||
public get showApplauseBadge(): boolean {
|
||||
return this.showApplauseLevel && this.applauseLevel > 0 && (!this.showJitsiWindow || !this.isApplausBarUsed);
|
||||
}
|
||||
|
||||
private get applauseType(): ApplauseType {
|
||||
return this.applauseService.applauseType;
|
||||
}
|
||||
|
||||
public get isApplauseTypeBar(): boolean {
|
||||
return this.applauseType === ApplauseType.bar;
|
||||
}
|
||||
|
||||
public get isApplauseTypeParticles(): boolean {
|
||||
return this.applauseType === ApplauseType.particles;
|
||||
}
|
||||
|
||||
private configOverwrite = {
|
||||
startAudioOnly: false,
|
||||
// allows jitsi on mobile devices
|
||||
@ -237,7 +262,8 @@ export class JitsiComponent extends BaseViewComponentDirective implements OnInit
|
||||
private constantsService: ConstantsService,
|
||||
private configService: ConfigService,
|
||||
private closService: CurrentListOfSpeakersService,
|
||||
private userMediaPermService: UserMediaPermService
|
||||
private userMediaPermService: UserMediaPermService,
|
||||
private applauseService: ApplauseService
|
||||
) {
|
||||
super(titleService, translate, snackBar);
|
||||
}
|
||||
@ -350,6 +376,22 @@ export class JitsiComponent extends BaseViewComponentDirective implements OnInit
|
||||
}),
|
||||
this.configService.get<boolean>('general_system_conference_open_video').subscribe(open => {
|
||||
this.configOverwrite.startWithVideoMuted = !open;
|
||||
}),
|
||||
this.configService.get<boolean>('general_system_applause_enable').subscribe(enable => {
|
||||
this.showApplause = enable;
|
||||
}),
|
||||
this.configService.get<number>('general_system_stream_applause_timeout').subscribe(timeout => {
|
||||
this.applauseTimeout = (timeout || 1) * 1000;
|
||||
}),
|
||||
this.configService.get<boolean>('general_system_applause_show_level').subscribe(show => {
|
||||
this.showApplauseLevel = show;
|
||||
}),
|
||||
this.configService.get<any>('general_system_applause_type').subscribe(type => {
|
||||
if (type === 'applause-type-bar') {
|
||||
this.isApplausBarUsed = true;
|
||||
} else {
|
||||
this.isApplausBarUsed = false;
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
@ -362,7 +404,10 @@ export class JitsiComponent extends BaseViewComponentDirective implements OnInit
|
||||
map(los => los?.findUserIndexOnList(this.operator.user.id) ?? -1),
|
||||
distinctUntilChanged()
|
||||
)
|
||||
.subscribe(userLosIndex => this.autoJoinJitsiByLosIndex(userLosIndex))
|
||||
.subscribe(userLosIndex => this.autoJoinJitsiByLosIndex(userLosIndex)),
|
||||
this.applauseService.applauseLevelObservable.subscribe(applauseLevel => {
|
||||
this.applauseLevel = applauseLevel || 0;
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
@ -595,4 +640,12 @@ export class JitsiComponent extends BaseViewComponentDirective implements OnInit
|
||||
private setConferenceState(newState: ConferenceState): void {
|
||||
this.currentState = newState;
|
||||
}
|
||||
|
||||
public sendApplause(): void {
|
||||
this.applauseDisabled = true;
|
||||
this.applauseService.sendApplause();
|
||||
setTimeout(() => {
|
||||
this.applauseDisabled = false;
|
||||
}, this.applauseTimeout);
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,3 @@
|
||||
<div [osResized]="resizeSubject" class="particle-wrapper">
|
||||
<Particles id="particles" [options]="particlesOptions" (particlesLoaded)="particlesLoaded($event)"> </Particles>
|
||||
</div>
|
@ -0,0 +1,7 @@
|
||||
.particle-wrapper {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.tsparticles-canvas-el {
|
||||
pointer-events: none !important;
|
||||
}
|
@ -0,0 +1,26 @@
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { E2EImportsModule } from 'e2e-imports.module';
|
||||
|
||||
import { ParticleDisplayComponent } from './particle-display.component';
|
||||
|
||||
describe('ParticleDisplayComponent', () => {
|
||||
let component: ParticleDisplayComponent;
|
||||
let fixture: ComponentFixture<ParticleDisplayComponent>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [E2EImportsModule]
|
||||
}).compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(ParticleDisplayComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
@ -0,0 +1,233 @@
|
||||
import { Component, Input, OnInit, ViewEncapsulation } from '@angular/core';
|
||||
import { MatSnackBar } from '@angular/material/snack-bar';
|
||||
import { Title } from '@angular/platform-browser';
|
||||
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { Subject } from 'rxjs';
|
||||
import { auditTime } from 'rxjs/operators';
|
||||
import { Container, SizeMode } from 'tsparticles';
|
||||
import { Shape } from 'tsparticles/dist/Options/Classes/Particles/Shape/Shape';
|
||||
import { IImageShape } from 'tsparticles/dist/Options/Interfaces/Particles/Shape/IImageShape';
|
||||
import { Emitters } from 'tsparticles/dist/Plugins/Emitters/Emitters';
|
||||
|
||||
import { ApplauseService } from 'app/core/ui-services/applause.service';
|
||||
import { ConfigService } from 'app/core/ui-services/config.service';
|
||||
import { ElementSize } from 'app/shared/directives/resized.directive';
|
||||
import { BaseViewComponentDirective } from 'app/site/base/base-view';
|
||||
|
||||
@Component({
|
||||
selector: 'os-particle-display',
|
||||
templateUrl: './particle-display.component.html',
|
||||
styleUrls: ['./particle-display.component.scss'],
|
||||
encapsulation: ViewEncapsulation.None
|
||||
})
|
||||
export class ParticleDisplayComponent extends BaseViewComponentDirective {
|
||||
public resizeSubject = new Subject<ElementSize>();
|
||||
private resizeAuditTime = 200;
|
||||
|
||||
private particleContainer: Container;
|
||||
|
||||
public set particleImage(imageUrl: string) {
|
||||
this.setParticleImage(imageUrl);
|
||||
}
|
||||
|
||||
public set particleLevel(level: number) {
|
||||
this.setParticleLevel(level);
|
||||
}
|
||||
|
||||
private noAutomaticParticles = {
|
||||
value: 0
|
||||
};
|
||||
|
||||
private slowBlinkingOpacity = {
|
||||
value: 0.8,
|
||||
animation: {
|
||||
enable: true,
|
||||
speed: 1,
|
||||
sync: false,
|
||||
minimumValue: 0.3
|
||||
},
|
||||
random: {
|
||||
enable: true,
|
||||
minimumValue: 0.8
|
||||
}
|
||||
};
|
||||
|
||||
private imageOptions: IImageShape = {
|
||||
replace_color: false,
|
||||
replaceColor: false,
|
||||
src: '',
|
||||
width: 24,
|
||||
height: 24
|
||||
};
|
||||
|
||||
private customImageShape = {
|
||||
type: 'image',
|
||||
image: this.imageOptions
|
||||
};
|
||||
|
||||
private charShapeHearth = {
|
||||
type: 'char',
|
||||
options: {
|
||||
char: {
|
||||
fill: true,
|
||||
font: 'Verdana',
|
||||
weight: '200',
|
||||
style: '',
|
||||
value: ['❤']
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
private slightlyRandomSize: any = {
|
||||
value: 16,
|
||||
random: {
|
||||
enable: true,
|
||||
minimumValue: 10
|
||||
}
|
||||
};
|
||||
|
||||
private moveUpOptions = {
|
||||
enable: true,
|
||||
direction: 'top',
|
||||
speed: 1.0,
|
||||
angle: {
|
||||
offset: 45,
|
||||
value: 90
|
||||
},
|
||||
gravity: {
|
||||
enable: true,
|
||||
maxSpeed: 1.5,
|
||||
acceleration: -3
|
||||
},
|
||||
outModes: {
|
||||
left: 'bounce',
|
||||
right: 'bounce',
|
||||
top: 'destroy'
|
||||
}
|
||||
};
|
||||
|
||||
private slowRandomRotation = {
|
||||
value: 0,
|
||||
enable: true,
|
||||
direction: 'random',
|
||||
animation: {
|
||||
enable: true,
|
||||
speed: 9
|
||||
},
|
||||
random: {
|
||||
enable: true,
|
||||
minimumValue: 0
|
||||
}
|
||||
};
|
||||
|
||||
private randomColor = {
|
||||
value: 'random'
|
||||
};
|
||||
|
||||
private singleBottomEmitter = [
|
||||
{
|
||||
direction: 'top',
|
||||
rate: {
|
||||
quantity: 0,
|
||||
delay: 0.33
|
||||
},
|
||||
position: {
|
||||
x: 50,
|
||||
y: 100
|
||||
},
|
||||
size: {
|
||||
mode: SizeMode.percent,
|
||||
width: 100
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
public particlesOptions = {
|
||||
fpsLimit: 30,
|
||||
particles: {
|
||||
number: this.noAutomaticParticles,
|
||||
opacity: this.slowBlinkingOpacity,
|
||||
rotate: this.slowRandomRotation,
|
||||
move: this.moveUpOptions,
|
||||
color: this.randomColor,
|
||||
shape: this.charShapeHearth,
|
||||
size: this.slightlyRandomSize
|
||||
},
|
||||
emitters: this.singleBottomEmitter,
|
||||
detectRetina: true
|
||||
};
|
||||
|
||||
public constructor(
|
||||
title: Title,
|
||||
translate: TranslateService,
|
||||
matSnackBar: MatSnackBar,
|
||||
configService: ConfigService,
|
||||
private applauseService: ApplauseService
|
||||
) {
|
||||
super(title, translate, matSnackBar);
|
||||
this.subscriptions.push(
|
||||
this.resizeSubject.pipe(auditTime(this.resizeAuditTime)).subscribe(size => {
|
||||
this.updateParticleContainer(size);
|
||||
}),
|
||||
applauseService.applauseLevelObservable.subscribe(applause => {
|
||||
this.particleLevel = this.calcEmitterLevel(applause || 0);
|
||||
}),
|
||||
configService.get<string>('general_system_applause_particle_image').subscribe(particleImage => {
|
||||
this.particleImage = particleImage || undefined;
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
private setParticleImage(particleImage: string): void {
|
||||
if (particleImage) {
|
||||
this.imageOptions.src = particleImage;
|
||||
(this.particlesOptions.particles.shape as any) = this.customImageShape;
|
||||
} else {
|
||||
(this.particlesOptions.particles.shape as any) = this.charShapeHearth;
|
||||
}
|
||||
if (this.particleContainer) {
|
||||
this.particleContainer.options.particles.load(this.particlesOptions.particles as any);
|
||||
this.refresh();
|
||||
}
|
||||
}
|
||||
|
||||
private calcEmitterLevel(applauseLevel: number): number {
|
||||
if (!applauseLevel) {
|
||||
return 0;
|
||||
}
|
||||
let emitterLevel = this.applauseService.getApplauseQuote(applauseLevel);
|
||||
emitterLevel = Math.ceil(emitterLevel * 10);
|
||||
return emitterLevel;
|
||||
}
|
||||
|
||||
private setParticleLevel(level: number): void {
|
||||
if (this.particleContainer) {
|
||||
const emitters = this.particleContainer.plugins.get('emitters') as Emitters;
|
||||
if (emitters) {
|
||||
emitters.array[0].emitterOptions.rate.quantity = level;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private updateParticleContainer(size: ElementSize): void {
|
||||
if (!size.height || !size.width) {
|
||||
this.stop();
|
||||
} else {
|
||||
this.refresh();
|
||||
}
|
||||
}
|
||||
|
||||
private stop(): void {
|
||||
this.particleContainer?.stop();
|
||||
}
|
||||
|
||||
private refresh(): void {
|
||||
this.particleContainer?.refresh();
|
||||
}
|
||||
|
||||
public particlesLoaded(container: Container): void {
|
||||
this.particleContainer = container;
|
||||
this.refresh();
|
||||
}
|
||||
}
|
@ -0,0 +1,10 @@
|
||||
<div class="progress-wrapper">
|
||||
<mat-icon class="end-icon" *ngIf="endIcon"> {{ endIcon }} </mat-icon>
|
||||
<div class="slot">
|
||||
<ng-content></ng-content>
|
||||
</div>
|
||||
<div class="progress-bar">
|
||||
<div class="buffer bar"></div>
|
||||
<div class="progress bar" [style.height.%]="value"></div>
|
||||
</div>
|
||||
</div>
|
@ -0,0 +1,36 @@
|
||||
$bar-margin: 10px;
|
||||
$bar-border-radius: 15px;
|
||||
|
||||
.progress-wrapper {
|
||||
display: flex;
|
||||
height: 100%;
|
||||
flex-direction: column;
|
||||
|
||||
.end-icon {
|
||||
display: block;
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
.progress-bar {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
|
||||
.bar {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
margin-left: $bar-margin;
|
||||
margin-right: $bar-margin;
|
||||
border-radius: $bar-border-radius;
|
||||
}
|
||||
|
||||
.buffer {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.progress {
|
||||
transition: height 0.5s ease;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,26 @@
|
||||
@import '~@angular/material/theming';
|
||||
|
||||
@mixin os-progress-theme($theme) {
|
||||
$primary: map-get($theme, primary);
|
||||
$accent: map-get($theme, accent);
|
||||
|
||||
.progress-wrapper {
|
||||
background-color: mat-color($primary);
|
||||
}
|
||||
|
||||
.end-icon {
|
||||
color: mat-color($primary, default-contrast);
|
||||
}
|
||||
|
||||
.slot {
|
||||
color: mat-color($primary, default-contrast);
|
||||
}
|
||||
|
||||
.buffer {
|
||||
background-color: mat-color($primary, darker);
|
||||
}
|
||||
|
||||
.progress {
|
||||
background-color: mat-color($accent);
|
||||
}
|
||||
}
|
@ -0,0 +1,24 @@
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { ProgressComponent } from './progress.component';
|
||||
|
||||
describe('ProgressComponent', () => {
|
||||
let component: ProgressComponent;
|
||||
let fixture: ComponentFixture<ProgressComponent>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ProgressComponent]
|
||||
}).compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(ProgressComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
@ -0,0 +1,14 @@
|
||||
import { Component, Input } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'os-progress',
|
||||
templateUrl: './progress.component.html',
|
||||
styleUrls: ['./progress.component.scss']
|
||||
})
|
||||
export class ProgressComponent {
|
||||
@Input()
|
||||
public value = 0;
|
||||
|
||||
@Input()
|
||||
public endIcon: string;
|
||||
}
|
@ -1,4 +1,5 @@
|
||||
<div class="video-wrapper">
|
||||
<os-particle-display *ngIf="showParticles" class="applause-particles"></os-particle-display>
|
||||
<div class="player-container" [ngClass]="{ hide: !isUrlOnline }">
|
||||
<video #videoPlayer class="video-js" controls preload="none"></video>
|
||||
</div>
|
||||
|
@ -1,7 +1,17 @@
|
||||
.video-wrapper {
|
||||
position: relative;
|
||||
display: flex;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
min-height: 200px;
|
||||
|
||||
.applause-particles {
|
||||
position: absolute;
|
||||
display: block;
|
||||
pointer-events: none !important;
|
||||
width: 100px;
|
||||
height: 100%;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.is-offline-wrapper {
|
||||
width: 100%;
|
||||
|
@ -48,6 +48,9 @@ export class VjsPlayerComponent implements OnInit, OnDestroy {
|
||||
this.checkVideoUrl();
|
||||
}
|
||||
|
||||
@Input()
|
||||
public showParticles: boolean;
|
||||
|
||||
@Output()
|
||||
private started: EventEmitter<void> = new EventEmitter();
|
||||
|
||||
|
@ -1,10 +1,15 @@
|
||||
import { Directive, ElementRef, Input, OnInit } from '@angular/core';
|
||||
|
||||
import { ResizeSensor } from 'css-element-queries';
|
||||
import { Interface } from 'readline';
|
||||
import { Subject } from 'rxjs';
|
||||
|
||||
export interface ElementSize {
|
||||
width: number;
|
||||
height: number;
|
||||
}
|
||||
/**
|
||||
* This directive takes a Subject<void> as input and everytime the surrounding element
|
||||
* This directive takes a Subject<ElementSize> as input and everytime the surrounding element
|
||||
* was resized, the subject is fired.
|
||||
*
|
||||
* Usage:
|
||||
@ -15,7 +20,7 @@ import { Subject } from 'rxjs';
|
||||
})
|
||||
export class ResizedDirective implements OnInit {
|
||||
@Input()
|
||||
public osResized: Subject<void>;
|
||||
public osResized: Subject<ElementSize>;
|
||||
|
||||
/**
|
||||
* Old width, to check, if the width has actually changed.
|
||||
@ -54,7 +59,10 @@ export class ResizedDirective implements OnInit {
|
||||
this.oldHeight = newHeight;
|
||||
|
||||
if (this.osResized) {
|
||||
this.osResized.next();
|
||||
this.osResized.next({
|
||||
width: newWidth,
|
||||
height: newHeight
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -130,6 +130,10 @@ import { JitsiComponent } from './components/jitsi/jitsi.component';
|
||||
import { VjsPlayerComponent } from './components/vjs-player/vjs-player.component';
|
||||
import { LiveStreamComponent } from './components/live-stream/live-stream.component';
|
||||
import { ListOfSpeakersContentComponent } from './components/list-of-speakers-content/list-of-speakers-content.component';
|
||||
import { ApplauseDisplayComponent } from './components/applause-display/applause-display.component';
|
||||
import { ProgressComponent } from './components/progress/progress.component';
|
||||
import { NgParticlesModule } from 'ng-particles';
|
||||
import { ParticleDisplayComponent } from './components/particle-display/particle-display.component';
|
||||
|
||||
/**
|
||||
* Share Module for all "dumb" components and pipes.
|
||||
@ -194,7 +198,8 @@ import { ListOfSpeakersContentComponent } from './components/list-of-speakers-co
|
||||
PblNgridTargetEventsModule,
|
||||
PdfViewerModule,
|
||||
NgxMaterialTimepickerModule,
|
||||
ChartsModule
|
||||
ChartsModule,
|
||||
NgParticlesModule
|
||||
],
|
||||
exports: [
|
||||
FormsModule,
|
||||
@ -364,7 +369,10 @@ import { ListOfSpeakersContentComponent } from './components/list-of-speakers-co
|
||||
JitsiComponent,
|
||||
VjsPlayerComponent,
|
||||
LiveStreamComponent,
|
||||
ListOfSpeakersContentComponent
|
||||
ListOfSpeakersContentComponent,
|
||||
ApplauseDisplayComponent,
|
||||
ProgressComponent,
|
||||
ParticleDisplayComponent
|
||||
],
|
||||
providers: [
|
||||
{
|
||||
|
@ -13,7 +13,6 @@
|
||||
</os-head-bar>
|
||||
|
||||
<mat-card class="spacer-bottom-60" [ngClass]="isEditing ? 'os-form-card' : 'os-card'">
|
||||
<mat-card [ngClass]="isEditing ? 'os-form-card' : 'os-card'">
|
||||
<ng-container *ngIf="!isEditing">
|
||||
<div class="app-content">
|
||||
<h1>{{ startContent.general_event_welcome_title | translate }}</h1>
|
||||
|
@ -9,43 +9,44 @@ $narrow-spacing: (
|
||||
);
|
||||
|
||||
/** Import brand theme */
|
||||
@import './assets/styles/themes/default-dark.scss';
|
||||
@import './assets/styles/themes/default-light.scss';
|
||||
@import './assets/styles/themes/green-dark.scss';
|
||||
@import './assets/styles/themes/green-light.scss';
|
||||
@import './assets/styles/themes/red-dark.scss';
|
||||
@import './assets/styles/themes/red-light.scss';
|
||||
@import './assets/styles/themes/solarized-dark.scss';
|
||||
@import '~assets/styles/themes/default-dark.scss';
|
||||
@import '~assets/styles/themes/default-light.scss';
|
||||
@import '~assets/styles/themes/green-dark.scss';
|
||||
@import '~assets/styles/themes/green-light.scss';
|
||||
@import '~assets/styles/themes/red-dark.scss';
|
||||
@import '~assets/styles/themes/red-light.scss';
|
||||
@import '~assets/styles/themes/solarized-dark.scss';
|
||||
|
||||
/** Global component style definition */
|
||||
@import './assets/styles/global-components-style.scss';
|
||||
@import '~assets/styles/global-components-style.scss';
|
||||
|
||||
/** Import the component-related style sheets here */
|
||||
@import './app/site/site.component.scss-theme.scss';
|
||||
@import './app/shared/components/projector-button/projector-button.component.scss';
|
||||
@import './app/site/agenda/components/list-of-speakers/list-of-speakers.component.scss-theme.scss';
|
||||
@import './app/shared/components/sorting-tree/sorting-tree.component.scss';
|
||||
@import './app/shared/components/global-spinner/global-spinner.component.scss';
|
||||
@import './app/shared/components/tile/tile.component.scss';
|
||||
@import './app/shared/components/block-tile/block-tile.component.scss';
|
||||
@import './app/shared/components/icon-container/icon-container.component.scss';
|
||||
@import './app/site/common/components/start/start.component.scss';
|
||||
@import './app/site/mediafiles/components/mediafile-list/mediafile-list.component.scss-theme.scss';
|
||||
@import './app/site/common/components/super-search/super-search.component.scss';
|
||||
@import './app/shared/components/rounded-input/rounded-input.component.scss';
|
||||
@import './app/shared/components/meta-text-block/meta-text-block.component.scss';
|
||||
@import './app/site/config/components/config-field/config-field.component.scss-theme.scss';
|
||||
@import './app/site/motions/modules/motion-detail/components/amendment-create-wizard/amendment-create-wizard.components.scss-theme.scss';
|
||||
@import './app/site/motions/modules/motion-detail/components/motion-detail-diff/motion-detail-diff.component.scss-theme.scss';
|
||||
@import './app/shared/components/banner/banner.component.scss-theme.scss';
|
||||
@import './app/site/motions/modules/motion-poll/motion-poll/motion-poll.component.scss-theme.scss';
|
||||
@import './app/site/motions/modules/motion-poll/motion-poll-detail/motion-poll-detail.component.scss-theme.scss';
|
||||
@import './app/site/assignments/modules/assignment-poll/components/assignment-poll-detail/assignment-poll-detail-component.scss-theme.scss';
|
||||
@import './app/shared/components/progress-snack-bar/progress-snack-bar.component.scss-theme.scss';
|
||||
@import './app/shared/components/jitsi/jitsi.component.scss-theme.scss';
|
||||
@import './app/shared/components/list-view-table/list-view-table.component.scss-theme.scss';
|
||||
@import './app/site/common/components/user-statistics/user-statistics.component.scss-theme.scss';
|
||||
@import './app/site/login/components/login-wrapper/login-wrapper.component.scss-theme.scss';
|
||||
@import '~app/site/site.component.scss-theme.scss';
|
||||
@import '~app/shared/components/projector-button/projector-button.component.scss';
|
||||
@import '~app/site/agenda/components/list-of-speakers/list-of-speakers.component.scss-theme.scss';
|
||||
@import '~app/shared/components/sorting-tree/sorting-tree.component.scss';
|
||||
@import '~app/shared/components/global-spinner/global-spinner.component.scss';
|
||||
@import '~app/shared/components/tile/tile.component.scss';
|
||||
@import '~app/shared/components/block-tile/block-tile.component.scss';
|
||||
@import '~app/shared/components/icon-container/icon-container.component.scss';
|
||||
@import '~app/site/common/components/start/start.component.scss';
|
||||
@import '~app/site/mediafiles/components/mediafile-list/mediafile-list.component.scss-theme.scss';
|
||||
@import '~app/site/common/components/super-search/super-search.component.scss';
|
||||
@import '~app/shared/components/rounded-input/rounded-input.component.scss';
|
||||
@import '~app/shared/components/meta-text-block/meta-text-block.component.scss';
|
||||
@import '~app/site/config/components/config-field/config-field.component.scss-theme.scss';
|
||||
@import '~app/site/motions/modules/motion-detail/components/amendment-create-wizard/amendment-create-wizard.components.scss-theme.scss';
|
||||
@import '~app/site/motions/modules/motion-detail/components/motion-detail-diff/motion-detail-diff.component.scss-theme.scss';
|
||||
@import '~app/shared/components/banner/banner.component.scss-theme.scss';
|
||||
@import '~app/site/motions/modules/motion-poll/motion-poll/motion-poll.component.scss-theme.scss';
|
||||
@import '~app/site/motions/modules/motion-poll/motion-poll-detail/motion-poll-detail.component.scss-theme.scss';
|
||||
@import '~app/site/assignments/modules/assignment-poll/components/assignment-poll-detail/assignment-poll-detail-component.scss-theme.scss';
|
||||
@import '~app/shared/components/progress-snack-bar/progress-snack-bar.component.scss-theme.scss';
|
||||
@import '~app/shared/components/jitsi/jitsi.component.scss-theme.scss';
|
||||
@import '~app/shared/components/list-view-table/list-view-table.component.scss-theme.scss';
|
||||
@import '~app/site/common/components/user-statistics/user-statistics.component.scss-theme.scss';
|
||||
@import '~app/site/login/components/login-wrapper/login-wrapper.component.scss-theme.scss';
|
||||
@import '~app/shared/components/progress/progress.component.scss-theme.scss';
|
||||
|
||||
/** Mix the component-related style-rules. Every single custom style goes here */
|
||||
@mixin openslides-components-theme($theme) {
|
||||
@ -72,6 +73,7 @@ $narrow-spacing: (
|
||||
@include os-list-view-table-theme($theme);
|
||||
@include os-user-statistics-style($theme);
|
||||
@include os-login-wrapper-theme($theme);
|
||||
@include os-progress-theme($theme);
|
||||
}
|
||||
|
||||
.openslides-default-light-theme {
|
||||
@ -126,7 +128,7 @@ $narrow-spacing: (
|
||||
/**
|
||||
* Custom configuration for light themes
|
||||
*/
|
||||
[class^="openslides-"][class*="-light-theme"] {
|
||||
[class^='openslides-'][class*='-light-theme'] {
|
||||
.logo-container {
|
||||
img.dark {
|
||||
display: none;
|
||||
@ -140,7 +142,7 @@ $narrow-spacing: (
|
||||
/**
|
||||
* Custom configuration for dark themes
|
||||
*/
|
||||
[class^="openslides-"][class*="-dark-theme"] {
|
||||
[class^='openslides-'][class*='-dark-theme'] {
|
||||
color: white;
|
||||
.logo-container {
|
||||
img.dark {
|
||||
|
@ -1,4 +1,4 @@
|
||||
@import './assets/styles/color-palettes/os-blue';
|
||||
@import '.~assets/styles/color-palettes/os-blue';
|
||||
|
||||
$openslides-primary: mat-palette($openslides-blue);
|
||||
$openslides-accent: mat-palette($mat-light-blue);
|
||||
|
@ -1,4 +1,4 @@
|
||||
@import './assets/styles/color-palettes/os-blue';
|
||||
@import '~assets/styles/color-palettes/os-blue';
|
||||
|
||||
// Generate paletes using: https://material.io/design/color/
|
||||
// default values for mat-palette: $default: 500, $lighter: 100, $darker: 700.
|
||||
|
@ -1,4 +1,4 @@
|
||||
@import './assets/styles/color-palettes/os-green.scss';
|
||||
@import '~assets/styles/color-palettes/os-green.scss';
|
||||
|
||||
$openslides-primary: mat-palette($openslides-green);
|
||||
$openslides-accent: mat-palette($mat-amber);
|
||||
|
@ -1,4 +1,4 @@
|
||||
@import './assets/styles/color-palettes/os-green.scss';
|
||||
@import '~assets/styles/color-palettes/os-green.scss';
|
||||
|
||||
$openslides-primary: mat-palette($openslides-green);
|
||||
$openslides-accent: mat-palette($mat-amber);
|
||||
|
@ -1,4 +1,4 @@
|
||||
@import './assets/styles/color-palettes/os-red.scss';
|
||||
@import '~assets/styles/color-palettes/os-red.scss';
|
||||
|
||||
$openslides-primary: mat-palette($openslides-primary-red, 500, 300, 900);
|
||||
$openslides-accent: mat-palette($mat-amber);
|
||||
|
@ -1,4 +1,4 @@
|
||||
@import './assets/styles/color-palettes/os-red.scss';
|
||||
@import '~assets/styles/color-palettes/os-red.scss';
|
||||
|
||||
$openslides-primary: mat-palette($openslides-primary-red, 500, 300, 900);
|
||||
$openslides-accent: mat-palette($mat-amber);
|
||||
|
@ -1,5 +1,5 @@
|
||||
@import './assets/styles/color-palettes/os-cyan.scss';
|
||||
@import './assets/styles/color-palettes/os-gray.scss';
|
||||
@import '~assets/styles/color-palettes/os-cyan.scss';
|
||||
@import '~assets/styles/color-palettes/os-gray.scss';
|
||||
|
||||
$openslides-primary: mat-palette($openslides-grey);
|
||||
$openslides-accent: mat-palette($openslides-cyan);
|
||||
|
226
client/src/assets/svg/clapping_hands.svg
Normal file
226
client/src/assets/svg/clapping_hands.svg
Normal file
@ -0,0 +1,226 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
version="1.1"
|
||||
id="svg2"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
sodipodi:docname="clapping_hands.svg"
|
||||
inkscape:version="1.0.1 (3bc2e813f5, 2020-09-07)">
|
||||
<metadata
|
||||
id="metadata8">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title />
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<defs
|
||||
id="defs6">
|
||||
<linearGradient
|
||||
inkscape:collect="always"
|
||||
id="linearGradient1782">
|
||||
<stop
|
||||
style="stop-color:#0c2c40;stop-opacity:1"
|
||||
offset="0"
|
||||
id="stop1778" />
|
||||
<stop
|
||||
style="stop-color:#2ca2ec;stop-opacity:1"
|
||||
offset="1"
|
||||
id="stop1780" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
inkscape:collect="always"
|
||||
id="linearGradient1722">
|
||||
<stop
|
||||
style="stop-color:#f56055;stop-opacity:1;"
|
||||
offset="0"
|
||||
id="stop1718" />
|
||||
<stop
|
||||
style="stop-color:#f56055;stop-opacity:0;"
|
||||
offset="1"
|
||||
id="stop1720" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
inkscape:collect="always"
|
||||
id="linearGradient1505">
|
||||
<stop
|
||||
style="stop-color:#ffffff;stop-opacity:1;"
|
||||
offset="0"
|
||||
id="stop1501" />
|
||||
<stop
|
||||
style="stop-color:#ffffff;stop-opacity:0;"
|
||||
offset="1"
|
||||
id="stop1503" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
inkscape:collect="always"
|
||||
xlink:href="#linearGradient1505"
|
||||
id="linearGradient1507"
|
||||
x1="-72.521545"
|
||||
y1="-354.46579"
|
||||
x2="-72.521545"
|
||||
y2="-220.85248"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
gradientTransform="translate(4.5351563e-5,0.25821471)" />
|
||||
<linearGradient
|
||||
inkscape:collect="always"
|
||||
xlink:href="#linearGradient1722"
|
||||
id="linearGradient1724"
|
||||
x1="1637.6523"
|
||||
y1="687.29828"
|
||||
x2="1637.6523"
|
||||
y2="726.80103"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
gradientTransform="matrix(1,0,0,1.708266,4.6566406e-4,-480.80936)" />
|
||||
<radialGradient
|
||||
inkscape:collect="always"
|
||||
xlink:href="#linearGradient1782"
|
||||
id="radialGradient1784"
|
||||
cx="773.6098"
|
||||
cy="-57.234745"
|
||||
fx="773.6098"
|
||||
fy="-57.234745"
|
||||
r="507.56699"
|
||||
gradientTransform="matrix(0.78017444,0,0,0.14366746,326.05843,630.13805)"
|
||||
gradientUnits="userSpaceOnUse" />
|
||||
</defs>
|
||||
<sodipodi:namedview
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1"
|
||||
objecttolerance="10"
|
||||
gridtolerance="10"
|
||||
guidetolerance="10"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="1080"
|
||||
id="namedview4"
|
||||
showgrid="false"
|
||||
showguides="true"
|
||||
inkscape:guide-bbox="true"
|
||||
inkscape:snap-bbox="true"
|
||||
inkscape:bbox-paths="true"
|
||||
inkscape:bbox-nodes="true"
|
||||
inkscape:snap-bbox-edge-midpoints="true"
|
||||
inkscape:snap-bbox-midpoints="true"
|
||||
inkscape:object-paths="true"
|
||||
inkscape:snap-intersection-paths="true"
|
||||
inkscape:snap-smooth-nodes="true"
|
||||
inkscape:snap-midpoints="true"
|
||||
inkscape:snap-object-midpoints="true"
|
||||
inkscape:zoom="22.627417"
|
||||
inkscape:cx="19.730628"
|
||||
inkscape:cy="14.778162"
|
||||
inkscape:window-x="1920"
|
||||
inkscape:window-y="0"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="g2182"
|
||||
inkscape:snap-global="true"
|
||||
inkscape:document-rotation="0">
|
||||
<sodipodi:guide
|
||||
position="-8.5967379,25.493463"
|
||||
orientation="1,0"
|
||||
id="guide1229" />
|
||||
<sodipodi:guide
|
||||
position="-5.4316862,28.164944"
|
||||
orientation="0,-1"
|
||||
id="guide1231" />
|
||||
</sodipodi:namedview>
|
||||
<g
|
||||
id="g2441"
|
||||
transform="matrix(0.93524686,0,0,0.93524686,-1220.0337,-836.48596)"
|
||||
style="fill:#000000;fill-opacity:1">
|
||||
<g
|
||||
transform="matrix(0.76088502,0,0,0.76030764,311.54674,218.13833)"
|
||||
id="g2397"
|
||||
style="fill:#000000;fill-opacity:1;stroke:none;stroke-width:1.13311">
|
||||
<g
|
||||
id="g1964-4"
|
||||
transform="rotate(90,218.24928,1114.2299)"
|
||||
style="fill:#000000;fill-opacity:1;stroke:none;stroke-width:1.13311">
|
||||
<g
|
||||
id="g1948-2"
|
||||
style="fill:#000000;fill-opacity:1;stroke:none;stroke-width:1.13311">
|
||||
<g
|
||||
id="g1946-2"
|
||||
style="fill:#000000;fill-opacity:1;stroke:none;stroke-width:1.13311">
|
||||
<g
|
||||
id="g1944-4"
|
||||
style="fill:#000000;fill-opacity:1;stroke:none;stroke-width:1.13311">
|
||||
<path
|
||||
id="path1942-5"
|
||||
d="M 23,5.5 V 20 c 0,2.2 -1.8,4 -4,4 H 11.7 C 10.62,24 9.6,23.57 8.85,22.81 L 1,14.83 c 0,0 1.26,-1.23 1.3,-1.25 0.22,-0.19 0.49,-0.29 0.79,-0.29 0.22,0 0.42,0.06 0.6,0.16 C 3.73,13.46 8,15.91 8,15.91 V 4 C 8,3.17 8.67,2.5 9.5,2.5 10.33,2.5 11,3.17 11,4 v 7 h 1 V 1.5 C 12,0.67 12.67,0 13.5,0 14.33,0 15,0.67 15,1.5 V 11 h 1 V 2.5 C 16,1.67 16.67,1 17.5,1 18.33,1 19,1.67 19,2.5 V 11 h 1 V 5.5 C 20,4.67 20.67,4 21.5,4 22.33,4 23,4.67 23,5.5 Z"
|
||||
inkscape:connector-curvature="0"
|
||||
style="fill:#000000;fill-opacity:1;stroke:none;stroke-width:1.13311" />
|
||||
</g>
|
||||
<g
|
||||
transform="translate(5.4402324e-7,4.0928558e-5)"
|
||||
id="g1944-4-0"
|
||||
style="fill:#000000;fill-opacity:1;stroke:none;stroke-width:1.13311" />
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<g
|
||||
style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.894784;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
transform="matrix(1.1175883,0,0,1.1175883,-155.61227,-104.47506)"
|
||||
id="g2182">
|
||||
<g
|
||||
id="g860"
|
||||
style="opacity:1;stroke-width:0.855545"
|
||||
transform="matrix(1.0458649,0,0,1.0458649,-60.689016,-41.511804)">
|
||||
<path
|
||||
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-variant-east-asian:normal;font-feature-settings:normal;font-variation-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;shape-margin:0;inline-size:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.855541;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate;stop-color:#000000;stop-opacity:1"
|
||||
d="m 1326.2009,898.5852 -2.6143,2.61248 c 0.054,0.0477 0.1074,0.097 0.1593,0.14738 0.05,0.0525 0.099,0.10627 0.1459,0.16112 l 2.6168,-2.61488 z m -4.6234,-2.21282 -0.434,-3e-5 v 3.69727 c 0.1446,-0.006 0.2894,-0.006 0.434,4e-5 z m 7.1449,6.80056 -3.7002,3e-5 c 0.013,0.14447 0.013,0.28922 2e-4,0.43368 l 3.7,-2e-5 z"
|
||||
id="path862" />
|
||||
<path
|
||||
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-variant-east-asian:normal;font-feature-settings:normal;font-variation-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;shape-margin:0;inline-size:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.855541;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate;stop-color:#000000;stop-opacity:1"
|
||||
d="m 1321.1426,895.92578 a 0.44743473,0.44743473 0 0 0 -0.4473,0.44727 v 3.69726 a 0.44743473,0.44743473 0 0 0 0.4668,0.44727 c 0.1322,-0.005 0.2643,-0.006 0.3965,0 a 0.44743473,0.44743473 0 0 0 0.4668,-0.44727 v -3.69726 a 0.44743473,0.44743473 0 0 0 -0.4473,-0.44727 z m 5.0703,2.21289 a 0.44743473,0.44743473 0 0 0 -0.3281,0.13086 l -2.6153,2.61133 a 0.44743473,0.44743473 0 0 0 0.021,0.65234 c 0.049,0.0433 0.096,0.0877 0.1426,0.13282 0.041,0.0437 0.082,0.0873 0.1191,0.13086 a 0.44743473,0.44743473 0 0 0 0.6563,0.0254 l 2.6152,-2.61524 a 0.44743473,0.44743473 0 0 0 0,-0.63281 l -0.3086,-0.30664 a 0.44743473,0.44743473 0 0 0 -0.3027,-0.12891 z m -1.1914,4.58594 a 0.44743473,0.44743473 0 0 0 -0.4453,0.48828 c 0.011,0.1179 0.011,0.23523 0,0.35352 a 0.44743473,0.44743473 0 0 0 0.4453,0.48828 h 3.7012 a 0.44743473,0.44743473 0 0 0 0.4472,-0.44727 v -0.43359 a 0.44743473,0.44743473 0 0 0 -0.4472,-0.44922 z"
|
||||
id="path864" />
|
||||
</g>
|
||||
</g>
|
||||
<g
|
||||
transform="matrix(0.86183474,0,0,0.86183474,180.09301,126.9712)"
|
||||
id="g2397-4"
|
||||
style="fill:#000000;fill-opacity:1">
|
||||
<g
|
||||
id="g1964-4-5"
|
||||
transform="rotate(90,218.24928,1114.2299)"
|
||||
style="fill:#000000;fill-opacity:1">
|
||||
<g
|
||||
id="g1948-2-0"
|
||||
style="fill:#000000;fill-opacity:1">
|
||||
<g
|
||||
id="g1946-2-57"
|
||||
style="fill:#000000;fill-opacity:1">
|
||||
<g
|
||||
id="g1944-4-32"
|
||||
style="fill:#000000;fill-opacity:1" />
|
||||
<g
|
||||
transform="translate(5.4402324e-7,4.0928558e-5)"
|
||||
id="g1944-4-0-2"
|
||||
style="fill:#000000;fill-opacity:1" />
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<path
|
||||
style="fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.806028"
|
||||
d="M 6.5569914,2.9864148 0.87698077,8.56734 C 0.33613684,9.1006455 0.03031238,9.8273997 0.03031238,10.595361 v 5.189794 c 0,1.181491 0.73189623,2.199492 1.76396712,2.62764 -0.00888,-0.08924 -0.02737,-0.174463 -0.02737,-0.265356 v -5.191514 c 0,-0.955146 0.3829864,-1.864797 1.0569667,-2.529425 L 6.3743238,6.9356205 C 6.8910656,6.0338659 7.5323796,4.9178609 7.538295,4.8989906 7.6098007,4.7710011 7.6521007,4.6281034 7.6521007,4.4716724 7.6521007,4.2583484 7.5805947,4.066391 7.4451576,3.90996 7.4310282,3.8814651 6.5571713,2.9864148 6.5571713,2.9864148 Z m 3.8401146,4.9778558 c -0.02737,0.048433 -0.0459,0.080279 -0.07769,0.1361228 C 10.154314,8.3899921 9.9344832,8.7732978 9.7158851,9.1548918 9.5822184,9.3881797 9.5695934,9.4091143 9.4485527,9.6201187 h 5.6800103 c 0.116884,-0.1710937 0.201821,-0.3659799 0.201821,-0.5892814 0,-0.5901895 -0.476748,-1.0665667 -1.067387,-1.0665667 z"
|
||||
id="path1942-5-3" />
|
||||
</svg>
|
After Width: | Height: | Size: 12 KiB |
@ -1,18 +1,18 @@
|
||||
/** theming */
|
||||
@import './assets/styles/component-themes.scss';
|
||||
@import '~assets/styles/component-themes.scss';
|
||||
|
||||
/** fonts */
|
||||
@import './assets/styles/fonts.scss';
|
||||
@import '~assets/styles/fonts.scss';
|
||||
@import '~material-icon-font/dist/Material-Icons.css';
|
||||
|
||||
/** Videojs */
|
||||
@import '~video.js/dist/video-js.css';
|
||||
|
||||
/** Load projector specific SCSS values */
|
||||
@import './assets/styles/projector.scss';
|
||||
@import '~assets/styles/projector.scss';
|
||||
|
||||
/** Load global scss variables and device mixing */
|
||||
@import './assets/styles/variables.scss';
|
||||
@import '~assets/styles/variables.scss';
|
||||
|
||||
.pbl-ngrid-cell {
|
||||
.fill {
|
||||
@ -788,3 +788,7 @@ button.mat-menu-item.selected {
|
||||
.hide {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.hide-height {
|
||||
height: 0 !important;
|
||||
}
|
||||
|
@ -15,7 +15,6 @@ RUN apt-get -y update && apt-get install --no-install-recommends -y \
|
||||
libxmlsec1-dev \
|
||||
libxmlsec1-openssl \
|
||||
pkg-config
|
||||
|
||||
RUN rm -rf /var/lib/apt/lists/*
|
||||
|
||||
COPY requirements /app/requirements
|
||||
|
@ -169,6 +169,85 @@ def get_config_variables():
|
||||
subgroup="Live conference",
|
||||
)
|
||||
|
||||
# Applause
|
||||
|
||||
yield ConfigVariable(
|
||||
name="general_system_applause_enable",
|
||||
default_value=False,
|
||||
input_type="boolean",
|
||||
label="Enable virtual applause",
|
||||
help_text="Shows a 'Send applause' icon in the live stream bar",
|
||||
weight=170,
|
||||
subgroup="Virtual applause",
|
||||
)
|
||||
|
||||
yield ConfigVariable(
|
||||
name="general_system_applause_type",
|
||||
default_value="applause-type-bar",
|
||||
input_type="choice",
|
||||
choices=(
|
||||
{
|
||||
"value": "applause-type-bar",
|
||||
"display_name": "Bar",
|
||||
},
|
||||
{
|
||||
"value": "applause-type-particles",
|
||||
"display_name": "Particles",
|
||||
},
|
||||
),
|
||||
label="Applause Type",
|
||||
weight=171,
|
||||
subgroup="Virtual applause",
|
||||
)
|
||||
|
||||
yield ConfigVariable(
|
||||
name="general_system_applause_show_level",
|
||||
default_value=False,
|
||||
input_type="boolean",
|
||||
label="Show applause amount",
|
||||
weight=172,
|
||||
subgroup="Virtual applause",
|
||||
)
|
||||
|
||||
yield ConfigVariable(
|
||||
name="general_system_applause_min_amount",
|
||||
default_value=1,
|
||||
input_type="integer",
|
||||
label="Lowest applause amount",
|
||||
help_text="Lowest amount required for OpenSlides to recognize applause",
|
||||
weight=173,
|
||||
subgroup="Virtual applause",
|
||||
)
|
||||
|
||||
yield ConfigVariable(
|
||||
name="general_system_applause_max_amount",
|
||||
default_value=0,
|
||||
input_type="integer",
|
||||
label="Highest applause amount",
|
||||
help_text="Defines the maximum deflection of the amount. Entering zero will use the amount of present users instead.",
|
||||
weight=174,
|
||||
subgroup="Virtual applause",
|
||||
)
|
||||
|
||||
yield ConfigVariable(
|
||||
name="general_system_stream_applause_timeout",
|
||||
default_value=5,
|
||||
input_type="integer",
|
||||
label="Applause timeout in seconds",
|
||||
help_text="Determines how long a user has to wait to applaud again. Also determines the time in which applause is collected",
|
||||
weight=175,
|
||||
subgroup="Virtual applause",
|
||||
)
|
||||
|
||||
yield ConfigVariable(
|
||||
name="general_system_applause_particle_image",
|
||||
default_value="",
|
||||
label="Applause particle image url",
|
||||
help_text="Shows the given image as applause particle. Recommended image format: 24x24px, PNG, JPG or SVG",
|
||||
weight=176,
|
||||
subgroup="Virtual applause",
|
||||
)
|
||||
|
||||
# General System
|
||||
|
||||
yield ConfigVariable(
|
||||
|
Loading…
Reference in New Issue
Block a user