Add applause in client

Applause button in Jitsi Bar,
Add Applause Service,
Add Applause Display component,
Add Config varriables,
Integrate applause display component in Jitsi bar,
Integrate custom vertical progress bar as own component,
  - vertical and more customizable than the mat-progress-bar
  - includes an optional end icon
  - animated and themed
Add custom clapping icon
applause particles using tsparticles
custom particle component
dynamic add and remove functions to alter particles in runtime
Set own particle shape
Use smooth emitter for clean particle spawning
This commit is contained in:
Sean 2021-01-15 11:29:26 +01:00
parent 97950d5baa
commit e3d718cad0
41 changed files with 1946 additions and 1680 deletions

2431
client/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -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"
},

View File

@ -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')
);
}
}

View 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();
});
});

View 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;
}
}

View File

@ -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)]);

View File

@ -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>

View File

@ -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%;
}

View File

@ -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();
});
});

View File

@ -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;
})
);
}
}

View File

@ -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>

View File

@ -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%;

View File

@ -17,6 +17,10 @@
.indicator {
color: mat-color($primary, default-contrast);
svg path {
fill: mat-color($primary) !important;
}
}
.toggle-list-button {

View File

@ -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);
}
}

View File

@ -0,0 +1,3 @@
<div [osResized]="resizeSubject" class="particle-wrapper">
<Particles id="particles" [options]="particlesOptions" (particlesLoaded)="particlesLoaded($event)"> </Particles>
</div>

View File

@ -0,0 +1,7 @@
.particle-wrapper {
height: 100%;
}
.tsparticles-canvas-el {
pointer-events: none !important;
}

View File

@ -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();
});
});

View File

@ -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();
}
}

View File

@ -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>

View File

@ -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;
}
}
}

View File

@ -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);
}
}

View File

@ -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();
});
});

View File

@ -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;
}

View File

@ -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>

View File

@ -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%;

View File

@ -48,6 +48,9 @@ export class VjsPlayerComponent implements OnInit, OnDestroy {
this.checkVideoUrl();
}
@Input()
public showParticles: boolean;
@Output()
private started: EventEmitter<void> = new EventEmitter();

View File

@ -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
});
}
}
}

View File

@ -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: [
{

View File

@ -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>

View File

@ -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 {

View File

@ -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);

View File

@ -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.

View File

@ -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);

View File

@ -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);

View File

@ -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);

View File

@ -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);

View File

@ -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);

View 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

View File

@ -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;
}

View File

@ -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

View File

@ -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(