Merge remote-tracking branch 'origin/master' into RED-3796

This commit is contained in:
Adina Țeudan 2022-05-10 17:07:32 +03:00
commit 19f6b057e7
56 changed files with 912 additions and 681 deletions

View File

@ -16,6 +16,14 @@
"resources": {
"files": ["/assets/**", "/*.(eot|svg|cur|jpg|png|webp|gif|otf|ttf|woff|woff2|ani)"]
}
},
{
"name": "pdftron",
"installMode": "prefetch",
"updateMode": "prefetch",
"resources": {
"files": ["/assets/wv-resources/**/*.*"]
}
}
]
}

View File

@ -38,13 +38,13 @@ export class AnnotationPermissions {
permissions.canMarkAsFalsePositive = annotation.canBeMarkedAsFalsePositive && annotationEntity.hasDictionary;
permissions.canRemoveOrSuggestToRemoveOnlyHere =
(annotation.isRedacted || annotation.isHint) && !annotation.pending && !annotation.isImage;
!annotation.pending && (annotation.isRedacted || (annotation.isHint && !annotation.isImage));
permissions.canRemoveOrSuggestToRemoveFromDictionary =
annotation.isModifyDictionary &&
(annotation.isRedacted || annotation.isSkipped || annotation.isHint) &&
!annotation.pending;
permissions.canChangeLegalBasis = annotation.isRedacted && !annotation.imported && !annotation.pending;
permissions.canChangeLegalBasis = annotation.isRedacted && !annotation.pending;
permissions.canRecategorizeImage =
((annotation.isImage && !annotation.isSuggestion) || annotation.isSuggestionRecategorizeImage) && !annotation.pending;

View File

@ -1,4 +1,4 @@
import { annotationTypesTranslations } from '../../translations/annotation-types-translations';
import { annotationTypesTranslations, SuggestionAddFalsePositive } from '../../translations/annotation-types-translations';
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
import { Highlight, IComment, IManualChange, IPoint, IRectangle, LogEntryStatus, ManualRedactionType } from '@red/domain';
import { RedactionLogEntry } from '@models/file/redaction-log.entry';
@ -292,11 +292,18 @@ export class AnnotationWrapper implements IListable, Record<string, unknown> {
this._createContent(annotationWrapper, redactionLogEntry);
this._setSuperType(annotationWrapper, redactionLogEntry);
this._handleRecommendations(annotationWrapper, redactionLogEntry);
annotationWrapper.typeLabel = annotationTypesTranslations[annotationWrapper.superType];
annotationWrapper.typeLabel = this.#getTypeLabel(redactionLogEntry, annotationWrapper);
return annotationWrapper;
}
static #getTypeLabel(redactionLogEntry: RedactionLogEntry, annotation: AnnotationWrapper): string {
if (redactionLogEntry.reason?.toLowerCase() === 'false positive') {
return annotationTypesTranslations[SuggestionAddFalsePositive];
}
return annotationTypesTranslations[annotation.superType];
}
private static _handleRecommendations(annotationWrapper: AnnotationWrapper, redactionLogEntry: RedactionLogEntry) {
if (annotationWrapper.superType === SuperTypes.Recommendation) {
annotationWrapper.recommendationType = redactionLogEntry.type;

View File

@ -7,7 +7,6 @@ import { PendingChangesGuard } from '@guards/can-deactivate.guard';
import { FileAttributesListingScreenComponent } from './screens/file-attributes-listing/file-attributes-listing-screen.component';
import { DefaultColorsScreenComponent } from './screens/default-colors/default-colors-screen.component';
import { UserListingScreenComponent } from './screens/user-listing/user-listing-screen.component';
import { LicenseInformationScreenComponent } from './screens/license-information/license-information-screen.component';
import { DigitalSignatureScreenComponent } from './screens/digital-signature/digital-signature-screen.component';
import { AuditScreenComponent } from './screens/audit/audit-screen.component';
import { RouterModule, Routes } from '@angular/router';
@ -26,125 +25,127 @@ import { ACTIVE_DOSSIERS_SERVICE } from '../../tokens';
import { BaseEntityScreenComponent } from './base-entity-screen/base-entity-screen.component';
import { PermissionsGuard } from '../../guards/permissions-guard';
const routes: Routes = [
{ path: '', redirectTo: 'dossier-templates', pathMatch: 'full' },
const dossierTemplateIdRoutes = [
{
path: 'dossier-templates',
path: 'info',
canActivate: [CompositeRouteGuard],
component: BaseDossierTemplateScreenComponent,
loadChildren: () => import('./screens/info/dossier-template-info.module').then(m => m.DossierTemplateInfoModule),
},
{
path: 'entities',
children: [
{
path: '',
component: BaseAdminScreenComponent,
component: EntitiesListingScreenComponent,
canActivate: [CompositeRouteGuard],
data: {
routeGuards: [AuthGuard, RedRoleGuard],
},
loadChildren: () =>
import('./screens/dossier-templates-listing/dossier-templates-listing.module').then(
m => m.DossierTemplatesListingModule,
),
},
{
path: `:${DOSSIER_TEMPLATE_ID}`,
children: [
{
path: 'info',
canActivate: [CompositeRouteGuard],
component: BaseDossierTemplateScreenComponent,
loadChildren: () => import('./screens/info/dossier-template-info.module').then(m => m.DossierTemplateInfoModule),
},
{
path: 'entities',
children: [
{
path: '',
component: EntitiesListingScreenComponent,
canActivate: [CompositeRouteGuard],
data: {
routeGuards: [AuthGuard, RedRoleGuard],
},
},
{
path: `:${ENTITY_TYPE}`,
component: BaseEntityScreenComponent,
canActivate: [CompositeRouteGuard],
loadChildren: () => import('./screens/entities/entities.module').then(m => m.EntitiesModule),
data: {
routeGuards: [AuthGuard, RedRoleGuard, EntityExistsGuard],
},
},
],
},
{
path: 'rules',
component: BaseDossierTemplateScreenComponent,
canActivate: [CompositeRouteGuard],
data: {
routeGuards: [AuthGuard, RedRoleGuard],
},
loadChildren: () => import('./screens/rules/rules.module').then(m => m.RulesModule),
},
{
path: 'file-attributes',
component: FileAttributesListingScreenComponent,
canActivate: [CompositeRouteGuard],
data: {
routeGuards: [AuthGuard, RedRoleGuard],
},
},
{
path: 'watermark',
component: BaseDossierTemplateScreenComponent,
canActivate: [CompositeRouteGuard],
data: {
routeGuards: [AuthGuard, RedRoleGuard],
},
loadChildren: () => import('./screens/watermark/watermark.module').then(m => m.WatermarkModule),
},
{
path: 'reports',
component: BaseDossierTemplateScreenComponent,
canActivate: [CompositeRouteGuard],
data: {
routeGuards: [AuthGuard, RedRoleGuard],
},
loadChildren: () => import('./screens/reports/reports.module').then(m => m.ReportsModule),
},
{
path: 'dossier-attributes',
component: DossierAttributesListingScreenComponent,
canActivate: [CompositeRouteGuard],
data: {
routeGuards: [AuthGuard, RedRoleGuard],
},
},
{
path: 'dossier-states',
component: DossierStatesListingScreenComponent,
canActivate: [CompositeRouteGuard],
data: {
routeGuards: [AuthGuard, RedRoleGuard],
},
},
{
path: 'default-colors',
component: DefaultColorsScreenComponent,
canActivate: [CompositeRouteGuard],
data: {
routeGuards: [AuthGuard, RedRoleGuard],
},
},
{
path: 'justifications',
component: BaseDossierTemplateScreenComponent,
canActivate: [CompositeRouteGuard],
loadChildren: () => import('./screens/justifications/justifications.module').then(m => m.JustificationsModule),
},
{ path: '', redirectTo: 'info', pathMatch: 'full' },
],
path: `:${ENTITY_TYPE}`,
component: BaseEntityScreenComponent,
canActivate: [CompositeRouteGuard],
data: { routeGuards: [DossierTemplateExistsGuard] },
loadChildren: () => import('./screens/entities/entities.module').then(m => m.EntitiesModule),
data: {
routeGuards: [AuthGuard, RedRoleGuard, EntityExistsGuard],
},
},
],
},
{
path: 'rules',
component: BaseDossierTemplateScreenComponent,
canActivate: [CompositeRouteGuard],
data: {
routeGuards: [AuthGuard, RedRoleGuard],
},
loadChildren: () => import('./screens/rules/rules.module').then(m => m.RulesModule),
},
{
path: 'file-attributes',
component: FileAttributesListingScreenComponent,
canActivate: [CompositeRouteGuard],
data: {
routeGuards: [AuthGuard, RedRoleGuard],
},
},
{
path: 'watermark',
component: BaseDossierTemplateScreenComponent,
canActivate: [CompositeRouteGuard],
data: {
routeGuards: [AuthGuard, RedRoleGuard],
},
loadChildren: () => import('./screens/watermark/watermark.module').then(m => m.WatermarkModule),
},
{
path: 'reports',
component: BaseDossierTemplateScreenComponent,
canActivate: [CompositeRouteGuard],
data: {
routeGuards: [AuthGuard, RedRoleGuard],
},
loadChildren: () => import('./screens/reports/reports.module').then(m => m.ReportsModule),
},
{
path: 'dossier-attributes',
component: DossierAttributesListingScreenComponent,
canActivate: [CompositeRouteGuard],
data: {
routeGuards: [AuthGuard, RedRoleGuard],
},
},
{
path: 'dossier-states',
component: DossierStatesListingScreenComponent,
canActivate: [CompositeRouteGuard],
data: {
routeGuards: [AuthGuard, RedRoleGuard],
},
},
{
path: 'default-colors',
component: DefaultColorsScreenComponent,
canActivate: [CompositeRouteGuard],
data: {
routeGuards: [AuthGuard, RedRoleGuard],
},
},
{
path: 'justifications',
component: BaseDossierTemplateScreenComponent,
canActivate: [CompositeRouteGuard],
loadChildren: () => import('./screens/justifications/justifications.module').then(m => m.JustificationsModule),
},
{ path: '', redirectTo: 'info', pathMatch: 'full' },
];
const dossierTemplatesRoutes: Routes = [
{
path: '',
component: BaseAdminScreenComponent,
canActivate: [CompositeRouteGuard],
data: {
routeGuards: [AuthGuard, RedRoleGuard],
},
loadChildren: () =>
import('./screens/dossier-templates-listing/dossier-templates-listing.module').then(m => m.DossierTemplatesListingModule),
},
{
path: `:${DOSSIER_TEMPLATE_ID}`,
children: dossierTemplateIdRoutes,
canActivate: [CompositeRouteGuard],
data: { routeGuards: [DossierTemplateExistsGuard] },
},
];
const routes: Routes = [
{ path: '', redirectTo: 'dossier-templates', pathMatch: 'full' },
{
path: 'dossier-templates',
children: dossierTemplatesRoutes,
canActivate: [CompositeRouteGuard],
data: {
routeGuards: [AuthGuard, RedRoleGuard, DossierTemplatesGuard],
@ -173,12 +174,13 @@ const routes: Routes = [
},
{
path: 'license-info',
component: LicenseInformationScreenComponent,
component: BaseAdminScreenComponent,
canActivate: [CompositeRouteGuard],
data: {
routeGuards: [AuthGuard, RedRoleGuard],
requiredRoles: ['RED_ADMIN'],
},
loadChildren: () => import('./screens/license/license.module').then(m => m.LicenseModule),
},
{
path: 'digital-signature',

View File

@ -7,7 +7,6 @@ import { DefaultColorsScreenComponent } from './screens/default-colors/default-c
import { EntitiesListingScreenComponent } from './screens/entities-listing/entities-listing-screen.component';
import { DigitalSignatureScreenComponent } from './screens/digital-signature/digital-signature-screen.component';
import { FileAttributesListingScreenComponent } from './screens/file-attributes-listing/file-attributes-listing-screen.component';
import { LicenseInformationScreenComponent } from './screens/license-information/license-information-screen.component';
import { UserListingScreenComponent } from './screens/user-listing/user-listing-screen.component';
import { DossierTemplateBreadcrumbsComponent } from './components/dossier-template-breadcrumbs/dossier-template-breadcrumbs.component';
import { ColorPickerModule } from 'ngx-color-picker';
@ -15,8 +14,6 @@ import { AddEditFileAttributeDialogComponent } from './dialogs/add-edit-file-att
import { AddEditDossierTemplateDialogComponent } from './dialogs/add-edit-dossier-template-dialog/add-edit-dossier-template-dialog.component';
import { AddEntityDialogComponent } from './dialogs/add-entity-dialog/add-entity-dialog.component';
import { EditColorDialogComponent } from './dialogs/edit-color-dialog/edit-color-dialog.component';
import { ComboChartComponent, ComboSeriesVerticalComponent } from './components/combo-chart';
import { NgxChartsModule } from '@swimlane/ngx-charts';
import { AdminDialogService } from './services/admin-dialog.service';
import { GeneralConfigScreenComponent } from './screens/general-config/general-config-screen.component';
import { SmtpAuthDialogComponent } from './dialogs/smtp-auth-dialog/smtp-auth-dialog.component';
@ -24,7 +21,6 @@ import { AddEditUserDialogComponent } from './dialogs/add-edit-user-dialog/add-e
import { UsersStatsComponent } from './components/users-stats/users-stats.component';
import { FileAttributesCsvImportDialogComponent } from './dialogs/file-attributes-csv-import-dialog/file-attributes-csv-import-dialog.component';
import { ActiveFieldsListingComponent } from './dialogs/file-attributes-csv-import-dialog/active-fields-listing/active-fields-listing.component';
import { AdminSideNavComponent } from './admin-side-nav/admin-side-nav.component';
import { ResetPasswordComponent } from './dialogs/add-edit-user-dialog/reset-password/reset-password.component';
import { UserDetailsComponent } from './dialogs/add-edit-user-dialog/user-details/user-details.component';
import { AddEditDossierAttributeDialogComponent } from './dialogs/add-edit-dossier-attribute-dialog/add-edit-dossier-attribute-dialog.component';
@ -33,7 +29,6 @@ import { TrashScreenComponent } from './screens/trash/trash-screen.component';
import { AuditService } from './services/audit.service';
import { DigitalSignatureService } from './services/digital-signature.service';
import { BaseAdminScreenComponent } from './base-admin-screen/base-admin-screen.component';
import { LicenseReportService } from './services/licence-report.service';
import { RulesService } from './services/rules.service';
import { SmtpConfigService } from './services/smtp-config.service';
import { UploadDictionaryDialogComponent } from './dialogs/upload-dictionary-dialog/upload-dictionary-dialog.component';
@ -49,6 +44,7 @@ import { ConfirmDeleteDossierStateDialogComponent } from './dialogs/confirm-dele
import { TrashTableItemComponent } from './screens/trash/trash-table-item/trash-table-item.component';
import { BaseEntityScreenComponent } from './base-entity-screen/base-entity-screen.component';
import { CloneDossierTemplateDialogComponent } from './dialogs/clone-dossier-template-dialog/clone-dossier-template-dialog.component';
import { AdminSideNavComponent } from './admin-side-nav/admin-side-nav.component';
const dialogs = [
AddEditDossierTemplateDialogComponent,
@ -70,7 +66,6 @@ const screens = [
EntitiesListingScreenComponent,
DigitalSignatureScreenComponent,
FileAttributesListingScreenComponent,
LicenseInformationScreenComponent,
UserListingScreenComponent,
GeneralConfigScreenComponent,
DossierAttributesListingScreenComponent,
@ -79,11 +74,9 @@ const screens = [
const components = [
DossierTemplateBreadcrumbsComponent,
ComboChartComponent,
ComboSeriesVerticalComponent,
UsersStatsComponent,
ActiveFieldsListingComponent,
AdminSideNavComponent,
ActiveFieldsListingComponent,
ResetPasswordComponent,
UserDetailsComponent,
BaseAdminScreenComponent,
@ -104,7 +97,7 @@ const components = [
ConfirmDeleteDossierStateDialogComponent,
TrashTableItemComponent,
],
providers: [AdminDialogService, AuditService, DigitalSignatureService, LicenseReportService, RulesService, SmtpConfigService],
imports: [CommonModule, SharedModule, AdminRoutingModule, SharedAdminModule, NgxChartsModule, ColorPickerModule, A11yModule],
providers: [AdminDialogService, AuditService, DigitalSignatureService, RulesService, SmtpConfigService],
imports: [CommonModule, SharedModule, AdminRoutingModule, SharedAdminModule, ColorPickerModule, A11yModule],
})
export class AdminModule {}

View File

@ -1,184 +0,0 @@
import { Component, OnInit } from '@angular/core';
import { ConfigService } from '@services/config.service';
import { TranslateService } from '@ngx-translate/core';
import { ButtonConfig, IconButtonTypes, LoadingService } from '@iqser/common-ui';
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
import { UserService } from '@services/user.service';
import { RouterHistoryService } from '@services/router-history.service';
import { LicenseReportService } from '../../services/licence-report.service';
import { ILicenseReport } from '@red/domain';
import { Color, ScaleType } from '@swimlane/ngx-charts';
import { firstValueFrom } from 'rxjs';
import dayjs from 'dayjs';
@Component({
selector: 'redaction-license-information-screen',
templateUrl: './license-information-screen.component.html',
styleUrls: ['./license-information-screen.component.scss'],
})
export class LicenseInformationScreenComponent implements OnInit {
readonly currentUser = this._userService.currentUser;
readonly buttonConfigs: readonly ButtonConfig[] = [
{
label: _('license-info-screen.email-report'),
action: (): void => this.sendMail(),
type: IconButtonTypes.primary,
},
];
currentInfo: ILicenseReport = {};
totalInfo: ILicenseReport = {};
unlicensedInfo: ILicenseReport = {};
totalLicensedNumberOfPages = 0;
analysisPercentageOfLicense = 100;
barChart: any[];
lineChartSeries: any[] = [];
lineChartScheme: Color = {
name: 'Line chart scheme',
selectable: true,
group: ScaleType.Ordinal,
domain: ['#dd4d50', '#5ce594', '#0389ec'],
};
comboBarScheme: Color = {
name: 'Combo bar scheme',
selectable: true,
group: ScaleType.Ordinal,
domain: ['#0389ec'],
};
constructor(
readonly configService: ConfigService,
private readonly _userService: UserService,
private readonly _loadingService: LoadingService,
readonly routerHistoryService: RouterHistoryService,
private readonly _translateService: TranslateService,
private readonly _licenseReportService: LicenseReportService,
) {
_loadingService.start();
}
get currentYear(): number {
return new Date().getFullYear();
}
async ngOnInit() {
this.totalLicensedNumberOfPages = this.configService.values.LICENSE_PAGE_COUNT || 0;
const startDate = dayjs(this.configService.values.LICENSE_START, 'DD-MM-YYYY');
const endDate = dayjs(this.configService.values.LICENSE_END, 'DD-MM-YYYY');
await this._setMonthlyStats(startDate, endDate);
const currentConfig = {
startDate: startDate.toDate(),
endDate: endDate.toDate(),
};
const promises = [
firstValueFrom(this._licenseReportService.licenseReport(currentConfig)),
firstValueFrom(this._licenseReportService.licenseReport({})),
];
if (endDate.isBefore(dayjs())) {
const unlicensedConfig = {
startDate: endDate.toDate(),
};
promises.push(firstValueFrom(this._licenseReportService.licenseReport(unlicensedConfig)));
}
Promise.all(promises).then(reports => {
[this.currentInfo, this.totalInfo, this.unlicensedInfo] = reports;
this._loadingService.stop();
this.analysisPercentageOfLicense =
this.totalLicensedNumberOfPages > 0
? (this.currentInfo.numberOfAnalyzedPages / this.totalLicensedNumberOfPages) * 100
: 100;
});
}
sendMail(): void {
const licenseCustomer = this.configService.values.LICENSE_CUSTOMER;
const subject = this._translateService.instant('license-info-screen.email.title', {
licenseCustomer,
});
const lineBreak = '%0D%0A';
const body = [
this._translateService.instant('license-info-screen.email.body.analyzed', {
pages: this.currentInfo.numberOfAnalyzedPages,
}),
this._translateService.instant('license-info-screen.email.body.licensed', {
pages: this.totalLicensedNumberOfPages,
}),
].join(lineBreak);
window.location.href = `mailto:${this.configService.values.LICENSE_EMAIL}?subject=${subject}&body=${body}`;
}
private async _setMonthlyStats(startDate: dayjs.Dayjs, endDate: dayjs.Dayjs) {
const [startMonth, startYear] = [startDate.month(), startDate.year()];
const [endMonth, endYear] = [endDate.month(), endDate.year()];
let m: number = startMonth;
let y: number = startYear;
const totalLicensedSeries = [];
const cumulativePagesSeries = [];
const promises = [];
while (m <= endMonth && y <= endYear) {
totalLicensedSeries.push({
name: `${dayjs.monthsShort()[m]} ${y}`,
value: this.totalLicensedNumberOfPages,
});
let nm = m + 1;
let ny = y;
if (nm === 12) {
nm = 0;
ny++;
}
promises.push(
firstValueFrom(
this._licenseReportService.licenseReport({
startDate: dayjs(`01-${m + 1}-${y}`, 'DD-M-YYYY').toDate(),
endDate: dayjs(`01-${nm + 1}-${ny}`, 'DD-M-YYYY').toDate(),
}),
),
);
y = ny;
m = nm;
}
const reports = await Promise.all(promises);
m = startMonth;
y = startYear;
let cumulativePages = 0;
this.barChart = [];
for (const report of reports) {
cumulativePages += report.numberOfAnalyzedPages;
this.barChart.push({
name: `${dayjs.monthsShort()[m]} ${y}`,
value: report.numberOfAnalyzedPages,
});
cumulativePagesSeries.push({
name: `${dayjs.monthsShort()[m]} ${y}`,
value: cumulativePages,
});
m++;
if (m === 12) {
m = 0;
y++;
}
}
this.lineChartSeries = [
{
name: this._translateService.instant('license-info-screen.chart.licensed-total'),
series: totalLicensedSeries,
},
{
name: this._translateService.instant('license-info-screen.chart.cumulative'),
series: cumulativePagesSeries,
},
];
}
}

View File

@ -0,0 +1,18 @@
<combo-chart-component
*ngIf="lineChartSeries$ | async as lineChartSeries"
[animations]="true"
[colorSchemeLine]="lineChartScheme"
[legendTitle]="'license-info-screen.chart.legend' | translate"
[legend]="true"
[lineChart]="lineChartSeries"
[results]="barChart"
[scheme]="comboBarScheme"
[showGridLines]="true"
[showRightYAxisLabel]="true"
[showYAxisLabel]="true"
[view]="[1000, 300]"
[xAxis]="true"
[yAxisLabelRight]="'license-info-screen.chart.total-pages' | translate"
[yAxisLabel]="'license-info-screen.chart.pages-per-month' | translate"
[yAxis]="true"
></combo-chart-component>

View File

@ -0,0 +1,127 @@
import { Component } from '@angular/core';
import { ComboBarScheme, LICENSE_STORAGE_KEY, LineChartScheme } from '../utils/constants';
import dayjs from 'dayjs';
import { TranslateService } from '@ngx-translate/core';
import { ILicenseReport } from '@red/domain';
import { LicenseService } from '../services/license.service';
import { IDateRange } from '../utils/date-range';
import { ILicense } from '../utils/license';
import { switchMap, tap } from 'rxjs/operators';
import { ILineChartSeries } from '../combo-chart/models';
import { LoadingService } from '@iqser/common-ui';
import { generateDateRanges, isCurrentMonth, toDate } from '../utils/functions';
const monthNames = dayjs.monthsShort();
@Component({
selector: 'redaction-license-chart',
templateUrl: './license-chart.component.html',
styleUrls: ['./license-chart.component.scss'],
})
export class LicenseChartComponent {
readonly lineChartScheme = LineChartScheme;
readonly comboBarScheme = ComboBarScheme;
lineChartSeries$ = this.#licenseChartSeries$;
barChart: any[];
constructor(
private readonly _translateService: TranslateService,
private readonly _licenseService: LicenseService,
private readonly _loadingService: LoadingService,
) {}
get #licenseChartSeries$() {
return this._licenseService.selectedLicense$.pipe(
tap(() => this._loadingService.start()),
switchMap(license => this.#setMonthlyStats(license)),
tap(() => this._loadingService.stop()),
);
}
async #setMonthlyStats(licence: ILicense): Promise<ILineChartSeries[]> {
const startDate = dayjs(licence.validFrom);
const endDate = dayjs(licence.validUntil);
const startMonth: number = startDate.month();
const startYear: number = startDate.year();
const dateRanges = generateDateRanges(startMonth, startYear, endDate.month(), endDate.year());
const reports = await this.#getReports(dateRanges);
return [
{
name: this._translateService.instant('license-info-screen.chart.licensed-total'),
series: this.#totalLicensedPagesSeries(dateRanges),
},
{
name: this._translateService.instant('license-info-screen.chart.cumulative'),
series: this.#setBarChartAndGetCumulativePageSeries(startMonth, startYear, reports),
},
];
}
#setBarChartAndGetCumulativePageSeries(month: number, year: number, reports: ILicenseReport[]) {
let cumulativePages = 0;
const cumulativePagesSeries = [];
this.barChart = [];
for (const report of reports) {
cumulativePages += report.numberOfAnalyzedPages;
const name = `${monthNames[month]} ${year}`;
this.barChart.push({
name,
value: report.numberOfAnalyzedPages,
});
cumulativePagesSeries.push({
name,
value: cumulativePages,
});
month++;
if (month === 12) {
month = 0;
year++;
}
}
return cumulativePagesSeries;
}
#getReports(dateRanges: IDateRange[]) {
const reports = dateRanges.map(dateRange => {
const startMonth = dateRange.startMonth + 1;
const endMonth = dateRange.endMonth + 1;
const key = `${startMonth}.${dateRange.startYear}-${endMonth}.${dateRange.endYear}`;
const existingReport = this._licenseService.storedReports[key];
if (existingReport) {
return existingReport;
}
const startDate = toDate(startMonth, dateRange.startYear);
const endDate = toDate(endMonth, dateRange.endYear);
const requestedReport = this._licenseService.getReport({ startDate, endDate });
return requestedReport.then(report => this.#storeReportIfNotCurrentMonth(dateRange, report, key));
});
return Promise.all(reports);
}
#storeReportIfNotCurrentMonth(dateRange: IDateRange, report: ILicenseReport, key: string) {
if (!isCurrentMonth(dateRange.startMonth + 1, dateRange.startYear)) {
this._licenseService.storedReports[key] = report;
localStorage.setItem(LICENSE_STORAGE_KEY, JSON.stringify(this._licenseService.storedReports));
}
return report;
}
#totalLicensedPagesSeries(dateRanges: IDateRange[]) {
return dateRanges.map(dateRange => ({
name: `${monthNames[dateRange.startMonth]} ${dateRange.startYear}`,
value: this._licenseService.totalLicensedNumberOfPages,
}));
}
}

View File

@ -1,8 +1,4 @@
<section class="settings">
<div class="overlay-shadow"></div>
<redaction-admin-side-nav type="settings"></redaction-admin-side-nav>
<div>
<iqser-page-header
(closeAction)="routerHistoryService.navigateToLastDossiersScreen()"
@ -38,19 +34,28 @@
<div class="section-title all-caps-label" translate="license-info-screen.licensing-details"></div>
<div class="row">
<div translate="license-info-screen.licensed-to"></div>
<div>{{ configService.values.LICENSE_CUSTOMER || '-' }}</div>
</div>
<div class="row">
<div translate="license-info-screen.licensing-period"></div>
<div *ngIf="userPreferenceService.areDevFeaturesEnabled" class="row">
<div class="flex align-center" translate="license-info-screen.license-title"></div>
<div>
{{ configService.values.LICENSE_START || '-' }} /
{{ configService.values.LICENSE_END || '-' }}
<redaction-license-select (valueChanges)="licenceChanged($event)"></redaction-license-select>
</div>
</div>
<ng-container *ngIf="licenseService.selectedLicense$ | async as selectedLicense">
<div class="row">
<div translate="license-info-screen.licensed-to"></div>
<div>{{ selectedLicense.licensedTo || '-' }}</div>
</div>
<div class="row">
<div translate="license-info-screen.licensing-period"></div>
<div>
{{ (selectedLicense.validFrom | date: 'dd-MM-YYYY') || '-' }} /
{{ (selectedLicense.validUntil | date: 'dd-MM-YYYY') || '-' }}
</div>
</div>
</ng-container>
<div class="row">
<div>{{ 'license-info-screen.licensed-page-count' | translate }}</div>
<div>{{ totalLicensedNumberOfPages }}</div>
@ -86,23 +91,7 @@
</div>
</div>
<combo-chart-component
[animations]="true"
[colorSchemeLine]="lineChartScheme"
[legendTitle]="'license-info-screen.chart.legend' | translate"
[legend]="true"
[lineChart]="lineChartSeries"
[results]="barChart"
[scheme]="comboBarScheme"
[showGridLines]="true"
[showRightYAxisLabel]="true"
[showYAxisLabel]="true"
[view]="[1000, 300]"
[xAxis]="true"
[yAxisLabelRight]="'license-info-screen.chart.total-pages' | translate"
[yAxisLabel]="'license-info-screen.chart.pages-per-month' | translate"
[yAxis]="true"
></combo-chart-component>
<redaction-license-chart></redaction-license-chart>
</div>
</div>
</div>

View File

@ -0,0 +1,95 @@
import { Component, OnInit } from '@angular/core';
import { ConfigService } from '@services/config.service';
import { TranslateService } from '@ngx-translate/core';
import { ButtonConfig, IconButtonTypes, LoadingService } from '@iqser/common-ui';
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
import { UserService } from '@services/user.service';
import { RouterHistoryService } from '@services/router-history.service';
import { LicenseService } from '../services/license.service';
import { ILicenseReport } from '@red/domain';
import dayjs from 'dayjs';
import { ILicense } from '../utils/license';
import { UserPreferenceService } from '../../../../../services/user-preference.service';
@Component({
templateUrl: './license-screen.component.html',
styleUrls: ['./license-screen.component.scss'],
})
export class LicenseScreenComponent implements OnInit {
readonly currentYear = new Date().getFullYear();
readonly currentUser = this._userService.currentUser;
readonly buttonConfigs: readonly ButtonConfig[] = [
{
label: _('license-info-screen.email-report'),
action: (): void => this.sendMail(),
type: IconButtonTypes.primary,
},
];
currentInfo: ILicenseReport = {};
totalInfo: ILicenseReport = {};
unlicensedInfo: ILicenseReport = {};
analysisPercentageOfLicense = 100;
totalLicensedNumberOfPages = this.licenseService.totalLicensedNumberOfPages;
constructor(
readonly configService: ConfigService,
readonly userPreferenceService: UserPreferenceService,
readonly licenseService: LicenseService,
private readonly _userService: UserService,
private readonly _loadingService: LoadingService,
readonly routerHistoryService: RouterHistoryService,
private readonly _translateService: TranslateService,
) {
_loadingService.start();
}
ngOnInit() {
return this.loadLicenceData(this.licenseService.selectedLicense);
}
async loadLicenceData(license: ILicense) {
const startDate = dayjs(license.validFrom);
const endDate = dayjs(license.validUntil);
const currentConfig = {
startDate: startDate.toDate(),
endDate: endDate.toDate(),
};
const reports: Promise<ILicenseReport>[] = [this.licenseService.getReport(currentConfig), this.licenseService.getReport({})];
if (endDate.isBefore(dayjs())) {
const unlicensedConfig = {
startDate: endDate.toDate(),
};
reports.push(this.licenseService.getReport(unlicensedConfig));
}
[this.currentInfo, this.totalInfo, this.unlicensedInfo] = await Promise.all(reports);
this.analysisPercentageOfLicense =
this.totalLicensedNumberOfPages > 0 ? (this.currentInfo.numberOfAnalyzedPages / this.totalLicensedNumberOfPages) * 100 : 100;
}
sendMail(): void {
const licenseCustomer = this.licenseService.selectedLicense.licensedTo;
const subject = this._translateService.instant('license-info-screen.email.title', {
licenseCustomer,
});
const lineBreak = '%0D%0A';
const body = [
this._translateService.instant('license-info-screen.email.body.analyzed', {
pages: this.currentInfo.numberOfAnalyzedPages,
}),
this._translateService.instant('license-info-screen.email.body.licensed', {
pages: this.totalLicensedNumberOfPages,
}),
].join(lineBreak);
const mail = this.licenseService.selectedLicense.licensedToEmail;
window.location.href = `mailto:${mail}?subject=${subject}&body=${body}`;
}
licenceChanged(license: ILicense) {
this.totalLicensedNumberOfPages = this.licenseService.totalLicensedNumberOfPages;
return this.loadLicenceData(license);
}
}

View File

@ -0,0 +1,21 @@
<div class="iqser-input-group w-400">
<mat-select (valueChange)="licenseChanged($event)" [(ngModel)]="value">
<mat-select-trigger>
<ng-container *ngTemplateOutlet="licenseInfo; context: { license: value }"></ng-container>
</mat-select-trigger>
<mat-option *ngFor="let license of licenseService.licenseData.licenses" [value]="license">
<ng-container *ngTemplateOutlet="licenseInfo; context: { license: this.license }"></ng-container>
</mat-option>
</mat-select>
</div>
<ng-template #licenseInfo let-license="license">
<div class="flex space-between align-center">
<span>{{ license.name }}</span>
<div class="mr-10 flex align-center">
<div [class.green]="license.id === licenseService.activeLicenseId" class="dot mr-4"></div>
<span class="small-label">{{ getStatus(license.id) | translate | uppercase }}</span>
</div>
</div>
</ng-template>

View File

@ -0,0 +1,15 @@
.green {
background: var(--iqser-green-2);
}
.space-between {
justify-content: space-between;
}
.dot {
position: relative;
}
.small-label {
font-weight: 600;
}

View File

@ -0,0 +1,31 @@
import { Component, EventEmitter, Output } from '@angular/core';
import { LicenseService } from '../services/license.service';
import { ILicense } from '../utils/license';
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
const translations = {
active: _('license-info-screen.status.active'),
inactive: _('license-info-screen.status.inactive'),
} as const;
@Component({
selector: 'redaction-license-select',
templateUrl: './license-select.component.html',
styleUrls: ['./license-select.component.scss'],
})
export class LicenseSelectComponent {
@Output() readonly valueChanges = new EventEmitter<ILicense>();
value = this.licenseService.getActiveLicense();
constructor(readonly licenseService: LicenseService) {}
getStatus(id) {
return id === this.licenseService.activeLicenseId ? translations.active : translations.inactive;
}
licenseChanged($event: ILicense) {
this.valueChanges.emit($event);
this.licenseService.selectedLicense$.next($event);
}
}

View File

@ -0,0 +1,32 @@
import { NgModule } from '@angular/core';
import { LicenseScreenComponent } from './license-screen/license-screen.component';
import { LicenseSelectComponent } from './license-select/license-select.component';
import { LicenseChartComponent } from './license-chart/license-chart.component';
import { LicenseService } from './services/license.service';
import { RouterModule, Routes } from '@angular/router';
import { TranslateModule } from '@ngx-translate/core';
import { MatSelectModule } from '@angular/material/select';
import { IqserListingModule } from '@iqser/common-ui';
import { NgxChartsModule } from '@swimlane/ngx-charts';
import { ComboChartComponent, ComboSeriesVerticalComponent } from './combo-chart';
import { FormsModule } from '@angular/forms';
const routes: Routes = [
{
path: '',
component: LicenseScreenComponent,
},
];
@NgModule({
declarations: [
LicenseScreenComponent,
LicenseSelectComponent,
LicenseChartComponent,
ComboChartComponent,
ComboSeriesVerticalComponent,
],
imports: [RouterModule.forChild(routes), TranslateModule, MatSelectModule, FormsModule, NgxChartsModule, IqserListingModule],
providers: [LicenseService],
})
export class LicenseModule {}

View File

@ -0,0 +1,81 @@
import { Injectable, Injector } from '@angular/core';
import { GenericService, QueryParam, RequiredParam, Validate } from '@iqser/common-ui';
import { ILicenseReport, ILicenseReportRequest } from '@red/domain';
import { BehaviorSubject, firstValueFrom } from 'rxjs';
import { ILicense, ILicenses, LICENSE_DATA } from '../utils/license';
import { ConfigService } from '../../../../../services/config.service';
import dayjs from 'dayjs';
import { getStoredReports } from '../utils/functions';
@Injectable()
export class LicenseService extends GenericService<ILicenseReport> {
storedReports = getStoredReports();
readonly licenseData = this.#licenceData;
readonly activeLicenseId = this.licenseData.activeLicense;
readonly selectedLicense$ = new BehaviorSubject<ILicense>(this.getActiveLicense());
constructor(protected readonly _injector: Injector, private readonly _configService: ConfigService) {
super(_injector, 'report');
}
get selectedLicense() {
return this.selectedLicense$.value;
}
get totalLicensedNumberOfPages() {
const processingPagesFeature = this.selectedLicense$.value.features.find(f => f.name === 'processingPages');
return Number(processingPagesFeature.value ?? '0');
}
get #licenceData(): ILicenses {
return {
...LICENSE_DATA,
licenses: [
...LICENSE_DATA.licenses,
{
id: 'guid-0',
name: this._configService.values.LICENSE_CUSTOMER,
product: 'RedactManager',
licensedTo: this._configService.values.LICENSE_CUSTOMER,
licensedToEmail: this._configService.values.LICENSE_EMAIL,
validFrom: dayjs(this._configService.values.LICENSE_START, 'DD-MM-YYYY').toISOString(),
validUntil: dayjs(this._configService.values.LICENSE_END, 'DD-MM-YYYY').toISOString(),
features: [
{
name: 'pdftron',
type: 'STRING',
value: 'base64 encoded pdftron webviewer license key',
},
{
name: 'processingPages',
type: 'NUMBER',
value: this._configService.values.LICENSE_PAGE_COUNT.toString(),
},
],
},
],
};
}
getActiveLicense() {
return this.licenseData.licenses.find(license => license.id === this.activeLicenseId);
}
@Validate()
getReport$(@RequiredParam() body: ILicenseReportRequest, limit?: number, offset?: number) {
const queryParams: QueryParam[] = [];
if (limit) {
queryParams.push({ key: 'limit', value: limit });
}
if (offset) {
queryParams.push({ key: 'offset', value: offset });
}
return this._post(body, `${this._defaultModelPath}/license`, queryParams);
}
getReport(body: ILicenseReportRequest, limit?: number, offset?: number) {
return firstValueFrom(this.getReport$(body, limit, offset));
}
}

View File

@ -0,0 +1,17 @@
import { Color, ScaleType } from '@swimlane/ngx-charts';
export const ComboBarScheme: Color = {
name: 'Combo bar scheme',
selectable: true,
group: ScaleType.Ordinal,
domain: ['#0389ec'],
};
export const LineChartScheme: Color = {
name: 'Line chart scheme',
selectable: true,
group: ScaleType.Ordinal,
domain: ['#dd4d50', '#5ce594', '#0389ec'],
};
export const LICENSE_STORAGE_KEY = 'redaction-license-reports';

View File

@ -0,0 +1,6 @@
export interface IDateRange {
readonly startMonth: number;
readonly startYear: number;
readonly endMonth: number;
readonly endYear: number;
}

View File

@ -0,0 +1,41 @@
import dayjs from 'dayjs';
import { IDateRange } from './date-range';
import { LICENSE_STORAGE_KEY } from './constants';
import { ILicenseReport } from '@red/domain';
export function toDate(month: number, year: number) {
return dayjs(`01-${month}-${year}`, 'DD-M-YYYY').toDate();
}
export function generateDateRanges(month: number, year: number, endMonth: number, endYear: number) {
const dates: IDateRange[] = [];
while (month <= endMonth && year <= endYear) {
let nextMonth = month + 1;
let nextYear = year;
if (nextMonth === 12) {
nextMonth = 0;
nextYear++;
}
dates.push({ startMonth: month, startYear: year, endMonth: nextMonth, endYear: nextYear });
year = nextYear;
month = nextMonth;
}
return dates;
}
export function getStoredReports() {
const rawStoredReports = localStorage.getItem(LICENSE_STORAGE_KEY);
return JSON.parse(rawStoredReports ?? '{}') as Record<string, ILicenseReport>;
}
export function isCurrentMonth(month: number, year: number) {
const now = dayjs();
const currentMonth = now.month() + 1;
const currentYear = now.year();
return month === currentMonth && year === currentYear;
}

View File

@ -0,0 +1,69 @@
export interface ILicenseFeature {
readonly name: string;
readonly type: string;
readonly value: string;
}
export interface ILicense {
readonly id: string;
readonly name: string;
readonly product: string;
readonly licensedTo: string;
readonly licensedToEmail: string;
readonly validFrom: string;
readonly validUntil: string;
readonly features: readonly ILicenseFeature[];
}
export interface ILicenses {
readonly activeLicense: string;
readonly licenses: readonly ILicense[];
}
export const LICENSE_DATA: ILicenses = {
activeLicense: 'guid-0',
licenses: [
{
id: 'guid-1',
name: '1 Year comulative (2022)',
product: 'RedactManager',
licensedTo: 'Customer company name 1',
licensedToEmail: 'customer@example.com',
validFrom: '2022-01-01T00:00:00.000Z',
validUntil: '2022-12-31T23:59:59.999Z',
features: [
{
name: 'pdftron',
type: 'STRING',
value: 'base64 encoded pdftron webviewer license key',
},
{
name: 'processingPages',
type: 'NUMBER',
value: '200000',
},
],
},
{
id: 'guid-2',
name: '2 Year comulative (2021)',
product: 'RedactManager',
licensedTo: 'Customer company name 2',
licensedToEmail: 'customer@example.com',
validFrom: '2021-01-01T00:00:00.000Z',
validUntil: '2021-12-31T23:59:59.999Z',
features: [
{
name: 'pdftron',
type: 'STRING',
value: 'base64 encoded pdftron webviewer license key',
},
{
name: 'processingPages',
type: 'NUMBER',
value: '100000',
},
],
},
],
};

View File

@ -1,24 +0,0 @@
import { Injectable, Injector } from '@angular/core';
import { GenericService, QueryParam, RequiredParam, Validate } from '@iqser/common-ui';
import { ILicenseReport, ILicenseReportRequest } from '@red/domain';
@Injectable()
export class LicenseReportService extends GenericService<ILicenseReport> {
constructor(protected readonly _injector: Injector) {
super(_injector, 'report');
}
@Validate()
licenseReport(@RequiredParam() body: ILicenseReportRequest, limit?: number, offset?: number) {
const queryParams: QueryParam[] = [];
if (limit) {
queryParams.push({ key: 'limit', value: limit });
}
if (offset) {
queryParams.push({ key: 'offset', value: offset });
}
return this._post(body, `${this._defaultModelPath}/license`, queryParams);
}
}

View File

@ -4,13 +4,15 @@ import { SharedModule } from '@shared/shared.module';
import { DossierTemplateActionsComponent } from './components/dossier-template-actions/dossier-template-actions.component';
import { AddEditEntityComponent } from './components/add-edit-entity/add-edit-entity.component';
import { ColorPickerModule } from 'ngx-color-picker';
import { RouterModule } from '@angular/router';
const components = [DossierTemplateActionsComponent, AddEditEntityComponent];
const modules = [CommonModule, SharedModule];
@NgModule({
declarations: [...components],
exports: [...components],
providers: [],
imports: [CommonModule, SharedModule, ColorPickerModule],
exports: [...components, ...modules],
imports: [...modules, RouterModule, ColorPickerModule],
})
export class SharedAdminModule {}

View File

@ -23,7 +23,7 @@
[tooltip]="'dossier-details.edit-owner' | translate"
class="ml-14"
icon="iqser:edit"
iqserHelpMode="edit_dossier_owner"
iqserHelpMode="dashboard_in_dossier"
tooltipPosition="below"
></iqser-circle-button>
</ng-container>
@ -43,7 +43,7 @@
[strokeWidth]="15"
[subtitle]="'dossier-overview.dossier-details.charts.documents-in-dossier' | translate"
direction="row"
helpModeKey="filter_for_status"
helpModeKey="dashboard_in_dossier"
></redaction-simple-doughnut-chart>
</div>
@ -51,11 +51,7 @@
<iqser-progress-bar *ngFor="let config of statusConfig" [config]="config"></iqser-progress-bar>
</div>
<div
*ngIf="stats.hasFiles && needsWorkFilters$ | async as filters"
class="mt-32 legend pb-32"
iqserHelpMode="filter_for_editing_notes"
>
<div *ngIf="stats.hasFiles && needsWorkFilters$ | async as filters" class="mt-32 legend pb-32" iqserHelpMode="dashboard_in_dossier">
<div
(click)="filterService.toggleFilter('needsWorkFilters', filter.id)"
*ngFor="let filter of filters"

View File

@ -361,21 +361,21 @@ export class ConfigService {
label: this._translateService.instant('dossier-overview.quick-filters.assigned-to-me'),
checker: this._assignedToMeChecker,
disabled: entities.filter(this._assignedToMeChecker).length === 0,
helpModeKey: 'documents_quickfilter',
helpModeKey: 'filter_document_list',
},
{
id: 'unassigned',
label: this._translateService.instant('dossier-overview.quick-filters.unassigned'),
checker: this._unassignedChecker,
disabled: entities.filter(this._unassignedChecker).length === 0,
helpModeKey: 'documents_quickfilter',
helpModeKey: 'filter_document_list',
},
{
id: 'assigned-to-others',
label: this._translateService.instant('dossier-overview.quick-filters.assigned-to-others'),
checker: this._assignedToOthersChecker,
disabled: entities.filter(this._assignedToOthersChecker).length === 0,
helpModeKey: 'documents_quickfilter',
helpModeKey: 'filter_document_list',
},
].map(filter => new NestedFilter(filter));
}

View File

@ -83,6 +83,8 @@
[type]="iconButtonTypes.dark"
icon="iqser:trash"
id="deleteDossier"
iqserHelpMode="edit_dossier_delete_dossier"
[dialogElement]="true"
></iqser-icon-button>
<iqser-icon-button
@ -91,6 +93,8 @@
[label]="'dossier-listing.archive.action' | translate"
[type]="iconButtonTypes.dark"
icon="red:archive"
iqserHelpMode="edit_dossier_archive_dossier"
[dialogElement]="true"
></iqser-icon-button>
</div>
</form>

View File

@ -40,8 +40,7 @@ export class FileActionsComponent implements OnChanges {
@Input() dossier: Dossier;
@Input() type: 'file-preview' | 'dossier-overview-list' | 'dossier-overview-workflow';
@Input() maxWidth: number;
@Input() fileActionsHelpModeKey: 'document_features' | 'editor_document_features' = 'document_features';
@Input() fileActionsHelpModeKey: 'document_features_in_dossier' | 'document_features_in_editor' = 'document_features_in_dossier';
toggleTooltip?: string;
assignTooltip?: string;
buttonType?: CircleButtonType;

View File

@ -6,7 +6,7 @@
[scrollableParentView]="scrollableParentView"
[tooltip]="(currentUser.isManager ? 'dossier-listing.edit.action' : 'dossier-listing.dossier-info.action') | translate"
[type]="circleButtonTypes.dark"
iqserHelpMode="edit_dossier"
iqserHelpMode="edit_dossier_dossier_info"
></iqser-circle-button>
<iqser-circle-button

View File

@ -29,8 +29,6 @@
<iqser-circle-button
(action)="resize($event)"
*ngIf="annotationPermissions.canResizeAnnotation && annotations.length === 1"
[iqserHelpMode]="helpModeKey + '_resize'"
[scrollableParentView]="scrollableParentView"
[tooltipPosition]="tooltipPosition"
[tooltip]="'annotation-actions.resize.label' | translate"
[type]="buttonType"
@ -40,23 +38,19 @@
<iqser-circle-button
(action)="annotationActionsService.changeLegalBasis($event, annotations)"
*ngIf="annotationPermissions.canChangeLegalBasis"
[scrollableParentView]="scrollableParentView"
[tooltipPosition]="tooltipPosition"
[tooltip]="'annotation-actions.edit-reason.label' | translate"
[type]="buttonType"
icon="iqser:edit"
iqserHelpMode="redaction_edit_reason"
></iqser-circle-button>
<iqser-circle-button
(action)="acceptRecommendation($event)"
*ngIf="annotationPermissions.canAcceptRecommendation"
[scrollableParentView]="scrollableParentView"
[tooltipPosition]="tooltipPosition"
[tooltip]="'annotation-actions.accept-recommendation.label' | translate"
[type]="buttonType"
icon="iqser:check"
iqserHelpMode="recommendation_accept_or_reject"
></iqser-circle-button>
<iqser-circle-button
@ -107,7 +101,6 @@
<iqser-circle-button
(action)="annotationActionsService.recategorizeImages($event, annotations)"
*ngIf="annotationPermissions.canRecategorizeImage"
[iqserHelpMode]="helpModeKey + '_recategorize'"
[tooltipPosition]="tooltipPosition"
[tooltip]="'annotation-actions.recategorize-image' | translate"
[type]="buttonType"
@ -126,18 +119,15 @@
<iqser-circle-button
(action)="annotationActionsService.forceAnnotation($event, annotations)"
*ngIf="annotationPermissions.canForceRedaction"
[scrollableParentView]="scrollableParentView"
[tooltipPosition]="tooltipPosition"
[tooltip]="'annotation-actions.force-redaction.label' | translate"
[type]="buttonType"
icon="red:thumb-up"
iqserHelpMode="skipped_force_redaction"
></iqser-circle-button>
<iqser-circle-button
(action)="annotationActionsService.forceAnnotation($event, annotations, true)"
*ngIf="annotationPermissions.canForceHint"
[iqserHelpMode]="helpModeKey + '_force_hint'"
[tooltipPosition]="tooltipPosition"
[tooltip]="'annotation-actions.force-hint.label' | translate"
[type]="buttonType"
@ -147,7 +137,6 @@
<iqser-circle-button
(action)="hideAnnotation($event)"
*ngIf="isImage && isVisible"
[iqserHelpMode]="helpModeKey + '_hide'"
[tooltipPosition]="tooltipPosition"
[tooltip]="'annotation-actions.hide' | translate"
[type]="buttonType"
@ -166,8 +155,6 @@
<iqser-circle-button
(action)="removeOrSuggestRemoveAnnotation($event, true)"
*ngIf="annotationPermissions.canRemoveOrSuggestToRemoveFromDictionary"
[iqserHelpMode]="helpModeKey + '_remove_from_dictionary'"
[scrollableParentView]="scrollableParentView"
[tooltipPosition]="tooltipPosition"
[tooltip]="'annotation-actions.remove-annotation.remove-from-dict' | translate"
[type]="buttonType"
@ -177,8 +164,6 @@
<iqser-circle-button
(action)="markAsFalsePositive($event)"
*ngIf="annotationPermissions.canMarkAsFalsePositive"
[iqserHelpMode]="helpModeKey + '_false_positive'"
[scrollableParentView]="scrollableParentView"
[tooltipPosition]="tooltipPosition"
[tooltip]="'annotation-actions.remove-annotation.false-positive' | translate"
[type]="buttonType"
@ -188,8 +173,6 @@
<iqser-circle-button
(action)="removeOrSuggestRemoveAnnotation($event, false)"
*ngIf="annotationPermissions.canRemoveOrSuggestToRemoveOnlyHere"
[iqserHelpMode]="helpModeKey + '_remove_only_here'"
[scrollableParentView]="scrollableParentView"
[tooltipPosition]="tooltipPosition"
[tooltip]="'annotation-actions.remove-annotation.only-here' | translate"
[type]="buttonType"

View File

@ -78,22 +78,6 @@ export class AnnotationActionsComponent implements OnChanges {
return this.annotations?.length === 1 && this.annotations?.[0].resizing;
}
get scrollableParentView(): ScrollableParentView {
return ScrollableParentViews.ANNOTATIONS_LIST;
}
get helpModeKey() {
const type = this.annotations[0]?.typeLabel?.split('.')[1];
const typeValue = this.annotations[0]?.typeValue;
if (type === 'hint' && (typeValue === 'formula' || typeValue === 'image')) {
return 'image';
}
if (type === 'redaction' || type === 'manual-redaction') {
return 'redaction';
}
return type;
}
ngOnChanges(): void {
this._setPermissions();
this._changeRef.markForCheck();

View File

@ -3,7 +3,7 @@
<span [translateParams]="highlightGroup" [translate]="'highlights'" class="all-caps-label"></span>
</div>
<div *ngIf="(isWritable$ | async) && (multiSelectInactive$ | async)">
<div *ngIf="(isWritable$ | async) && (multiSelectInactive$ | async)" iqserHelpMode="highlights">
<iqser-circle-button
(action)="convertHighlights(highlightGroup)"
[size]="28"
@ -11,7 +11,6 @@
[type]="circleButtonTypes.dark"
class="mr-2"
icon="red:convert"
iqserHelpMode="convert_highlights"
tooltipPosition="above"
></iqser-circle-button>
@ -21,7 +20,6 @@
[tooltip]="'file-preview.highlights.remove' | translate"
[type]="circleButtonTypes.dark"
icon="iqser:trash"
iqserHelpMode="remove_highlights"
tooltipPosition="above"
></iqser-circle-button>
</div>

View File

@ -552,13 +552,19 @@ export class PdfViewerComponent extends AutoUnsubscribe implements OnInit, OnCha
return entry;
}
private _loadDocument(blob: Blob, file: File) {
private async _loadDocument(blob: Blob, file: File) {
const onError = () => {
this._loadingService.stop();
this._errorService.set(DocLoadingError);
this.stateService.reloadBlob();
};
this.instance.UI.loadDocument(blob, { filename: file?.filename + '.pdf' ?? 'document.pdf', onError });
const pdfNet = this.instance.Core.PDFNet;
await pdfNet.initialize(environment.licenseKey ? atob(environment.licenseKey) : null);
const document = await pdfNet.PDFDoc.createFromBuffer(await blob.arrayBuffer());
await document.flattenAnnotations(false);
this.instance.UI.loadDocument(document, { filename: file?.filename + '.pdf' ?? 'document.pdf', onError });
this._pageRotationService.clearRotationsHideActions();
}

View File

@ -35,7 +35,7 @@
[tooltip]="assignTooltip$ | async"
icon="iqser:edit"
tooltipPosition="below"
iqserHelpMode="assign_reviewer"
iqserHelpMode="document_features_in_editor"
></iqser-circle-button>
<iqser-circle-button

View File

@ -4,7 +4,7 @@
[class.active]="viewModeService.isStandard"
[matTooltip]="'file-preview.standard-tooltip' | translate"
class="red-tab"
iqserHelpMode="standard_view"
iqserHelpMode="view"
>
{{ 'file-preview.standard' | translate }}
</button>
@ -15,7 +15,7 @@
[disabled]="(canSwitchToDeltaView$ | async) === false"
[matTooltip]="'file-preview.delta-tooltip' | translate"
class="red-tab"
iqserHelpMode="delta_view"
iqserHelpMode="view"
>
{{ 'file-preview.delta' | translate }}
</button>
@ -26,7 +26,7 @@
[disabled]="(canSwitchToRedactedView$ | async) === false"
[matTooltip]="'file-preview.redacted-tooltip' | translate"
class="red-tab"
iqserHelpMode="preview_view"
iqserHelpMode="view"
>
{{ 'file-preview.redacted' | translate }}
</button>
@ -37,7 +37,7 @@
[disabled]="(canSwitchToHighlightsView$ | async) === false"
[matTooltip]="'file-preview.text-highlights-tooltip' | translate"
class="red-tab"
iqserHelpMode="highlights_view"
iqserHelpMode="view"
>
{{ 'file-preview.text-highlights' | translate }}
</button>

View File

@ -23,7 +23,7 @@
<redaction-file-actions
[dossier]="dossier"
[file]="file"
fileActionsHelpModeKey="editor_document_features"
fileActionsHelpModeKey="document_features_in_editor"
type="file-preview"
></redaction-file-actions>
@ -103,6 +103,8 @@
<redaction-annotation-actions
[annotations]="[annotation]"
[canPerformAnnotationActions]="canPerformAnnotationActions$ | async"
[iqserHelpMode]="getActionsHelpModeKey(annotation)"
[scrollableParentView]="scrollableParentView"
></redaction-annotation-actions>
</ng-template>

View File

@ -14,6 +14,8 @@ import {
OnAttach,
OnDetach,
processFilters,
ScrollableParentView,
ScrollableParentViews,
shareDistinctLast,
} from '@iqser/common-ui';
import { MatDialogRef, MatDialogState } from '@angular/material/dialog';
@ -52,6 +54,17 @@ import { NGXLogger } from 'ngx-logger';
import { StampService } from './services/stamp.service';
import Annotation = Core.Annotations.Annotation;
const HelpModeKeys = {
redaction: 'redaction_text',
'manual-redaction': 'redaction_text',
recommendation: 'recommendation',
skipped: 'skipped',
hint: 'hint_text',
'hint-ocr': 'hint_picture',
'hint-formula': 'picture',
'hint-image': 'image',
};
@Component({
templateUrl: './file-preview-screen.component.html',
styleUrls: ['./file-preview-screen.component.scss'],
@ -486,6 +499,19 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni
this.handleAnnotationSelected([]);
}
get scrollableParentView(): ScrollableParentView {
return ScrollableParentViews.ANNOTATIONS_LIST;
}
getActionsHelpModeKey(annotation: AnnotationWrapper): string {
const type = annotation?.typeLabel?.split('.')[1];
const typeValue = annotation?.typeValue;
if (type === 'hint' && (typeValue === 'ocr' || typeValue === 'formula' || typeValue === 'image')) {
return HelpModeKeys[`${type}-${typeValue}`];
}
return HelpModeKeys[type];
}
private _setExcludedPageStyles() {
const file = this._filesMapService.get(this.dossierId, this.fileId);
setTimeout(() => {

View File

@ -501,19 +501,26 @@ export class AnnotationActionsService {
}
private _getFalsePositiveText(annotation: AnnotationWrapper) {
if (annotation.canBeMarkedAsFalsePositive) {
let text: string;
if (annotation.hasTextAfter) {
text = getFirstRelevantTextPart(annotation.textAfter, 'FORWARD');
return text ? (annotation.value + text).trim() : annotation.value;
}
if (annotation.hasTextAfter) {
text = getFirstRelevantTextPart(annotation.textBefore, 'BACKWARD');
return text ? (text + annotation.value).trim() : annotation.value;
} else {
return annotation.value;
}
if (!annotation.canBeMarkedAsFalsePositive) {
return;
}
if (annotation.isRecommendation) {
return annotation.value;
}
let text: string;
if (annotation.hasTextAfter) {
text = getFirstRelevantTextPart(annotation.textAfter, 'FORWARD');
return text ? (annotation.value + text).trim() : annotation.value;
}
if (annotation.hasTextBefore) {
text = getFirstRelevantTextPart(annotation.textBefore, 'BACKWARD');
return text ? (text + annotation.value).trim() : annotation.value;
}
return annotation.value;
}
private _convertPath(path: string): string {

View File

@ -5,13 +5,13 @@
[attr.aria-expanded]="btn.ariaExpanded && btn.ariaExpanded | async"
[disabled]="btn.disabled"
[icon]="btn.icon"
[iqserHelpMode]="helpModeKey"
[scrollableParentView]="scrollableParentView"
[showDot]="btn.showDot"
[tooltipClass]="btn.tooltipClass"
[tooltipPosition]="tooltipPosition"
[tooltip]="btn.tooltip | translate"
[type]="btn.buttonType || buttonType"
[iqserHelpMode]="helpModeKey"
[scrollableParentView]="scrollableParentView"
></iqser-circle-button>
<!-- download redacted file-->
@ -19,11 +19,11 @@
*ngIf="btn.type === 'downloadBtn'"
[dossier]="btn.dossier"
[files]="btn.files"
[iqserHelpMode]="helpModeKey"
[scrollableParentView]="scrollableParentView"
[tooltipClass]="btn.tooltipClass"
[tooltipPosition]="tooltipPosition"
[type]="buttonType"
[iqserHelpMode]="helpModeKey"
[scrollableParentView]="scrollableParentView"
></redaction-file-download-btn>
<!-- exclude from redaction -->
@ -33,10 +33,10 @@
(click)="$event.stopPropagation()"
[checked]="btn.checked"
[disabled]="btn.disabled"
[iqserHelpMode]="helpModeKey"
[matTooltipPosition]="tooltipPosition"
[matTooltip]="btn.tooltip | translate"
[ngClass]="btn.class"
[iqserHelpMode]="helpModeKey"
[scrollableParentView]="scrollableParentView"
color="primary"
></mat-slide-toggle>

View File

@ -17,7 +17,7 @@ export class ExpandableFileActionsComponent implements OnChanges {
@Input() actions: Action[];
@Input() buttonType: CircleButtonType;
@Input() tooltipPosition: IqserTooltipPosition;
@Input() helpModeKey: 'document_features' | 'editor_document_features';
@Input() helpModeKey: 'document_features_in_dossier' | 'document_features_in_editor';
displayedButtons: Action[];
hiddenButtons: Action[];
@ -30,7 +30,7 @@ export class ExpandableFileActionsComponent implements OnChanges {
) {}
get scrollableParentView(): ScrollableParentView {
return this.helpModeKey === 'document_features' ? ScrollableParentViews.VIRTUAL_SCROLL : undefined;
return this.helpModeKey === 'document_features_in_dossier' ? ScrollableParentViews.VIRTUAL_SCROLL : undefined;
}
ngOnChanges(changes: SimpleChanges) {

View File

@ -26,7 +26,7 @@ export class SimpleDoughnutChartComponent implements OnChanges, OnInit {
@Input() totalType: 'sum' | 'count' | 'simpleLabel' = 'sum';
@Input() counterText: string;
@Input() filterKey = 'statusFilters';
@Input() helpModeKey: 'filter_for_status';
@Input() helpModeKey: 'dashboard_in_dossier';
filtersEnabled: boolean;
chartData: any[] = [];

View File

@ -1,7 +1,11 @@
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
import { SuperType, SuperTypes } from '@models/file/super-types';
export const annotationTypesTranslations: { [key in SuperType]: string } = {
export const SuggestionAddFalsePositive = 'suggestion-add-false-positive' as const;
type TranslationKey = SuperType & typeof SuggestionAddFalsePositive;
export const annotationTypesTranslations: Record<TranslationKey, string> = {
[SuperTypes.TextHighlight]: _('annotation-type.text-highlight'),
[SuperTypes.DeclinedSuggestion]: _('annotation-type.declined-suggestion'),
[SuperTypes.Hint]: _('annotation-type.hint'),
@ -19,4 +23,5 @@ export const annotationTypesTranslations: { [key in SuperType]: string } = {
[SuperTypes.SuggestionRemove]: _('annotation-type.suggestion-remove'),
[SuperTypes.SuggestionRemoveDictionary]: _('annotation-type.suggestion-remove-dictionary'),
[SuperTypes.SuggestionResize]: _('annotation-type.suggestion-resize'),
[SuggestionAddFalsePositive]: _('annotation-type.suggestion-add-false-positive'),
} as const;

View File

@ -1,30 +1,12 @@
{
"assign_reviewer": {
"en": "/en/index-en.html?contextId=assign_reviewer",
"de": "",
"it": "",
"fr": ""
},
"bulk_select_annotations": {
"en": "/en/index-en.html?contextId=bulk_select_annotations",
"de": "",
"it": "",
"fr": ""
},
"delta_view": {
"en": "/en/index-en.html?contextId=delta_view",
"de": "",
"it": "",
"fr": ""
},
"document_features": {
"en": "/en/index-en.html?contextId=document_features",
"de": "",
"it": "",
"fr": ""
},
"edit_dossier": {
"en": "/en/index-en.html?contextId=edit_dossier",
"document_features_in_dossier": {
"en": "/en/index-en.html?contextId=document_features_in_dossier",
"de": "",
"it": "",
"fr": ""
@ -41,14 +23,8 @@
"it": "",
"fr": ""
},
"edit_dossier_owner": {
"en": "/en/index-en.html?contextId=edit_dossier_owner",
"de": "",
"it": "",
"fr": ""
},
"redaction_edit_reason": {
"en": "/en/index-en.html?contextId=redaction_edit_reason",
"dashboard_in_dossier": {
"en": "/en/index-en.html?contextId=dashboard_in_dossier",
"de": "",
"it": "",
"fr": ""
@ -65,12 +41,6 @@
"it": "",
"fr": ""
},
"filter_for_editing_notes": {
"en": "/en/index-en.html?contextId=filter_for_editing_notes",
"de": "",
"it": "",
"fr": ""
},
"filter_for_status": {
"en": "/en/index-en.html?contextId=filter_for_status",
"de": "",
@ -101,104 +71,8 @@
"it": "",
"fr": ""
},
"preview_view": {
"en": "/en/index-en.html?contextId=delta_view",
"de": "",
"it": "",
"fr": ""
},
"highlights_view": {
"en": "/en/index-en.html?contextId=highlights_view",
"de": "",
"it": "",
"fr": ""
},
"convert_highlights": {
"en": "/en/index-en.html?contextId=convert_highlights",
"de": "",
"it": "",
"fr": ""
},
"remove_highlights": {
"en": "/en/index-en.html?contextId=remove_highlights",
"de": "",
"it": "",
"fr": ""
},
"recommendation_accept_or_reject": {
"en": "/en/index-en.html?contextId=recommendation_accept_or_reject",
"de": "",
"it": "",
"fr": ""
},
"redaction_false_positive": {
"en": "/en/index-en.html?contextId=redaction_false_positive",
"de": "",
"it": "",
"fr": ""
},
"recommendation_false_positive": {
"en": "/en/index-en.html?contextId=recommendation_false_positive",
"de": "",
"it": "",
"fr": ""
},
"skipped_remove_from_dictionary": {
"en": "/en/index-en.html?contextId=skipped_remove_from_dictionary",
"de": "",
"it": "",
"fr": ""
},
"hint_remove_from_dictionary": {
"en": "/en/index-en.html?contextId=hint_remove_from_dictionary",
"de": "",
"it": "",
"fr": ""
},
"recommendation_remove_from_dictionary": {
"en": "/en/index-en.html?contextId=recommendation_remove_from_dictionary",
"de": "",
"it": "",
"fr": ""
},
"redaction_remove_only_here": {
"en": "/en/index-en.html?contextId=redaction_remove_only_here",
"de": "",
"it": "",
"fr": ""
},
"hint_remove_only_here": {
"en": "/en/index-en.html?contextId=hint_remove_only_here",
"de": "",
"it": "",
"fr": ""
},
"hint_recategorize": {
"en": "/en/index-en.html?contextId=hint_recategorize",
"de": "",
"it": "",
"fr": ""
},
"hint_hide": {
"en": "/en/index-en.html?contextId=hint_hide",
"de": "",
"it": "",
"fr": ""
},
"hint_force_hint": {
"en": "/en/index-en.html?contextId=hint_force_hint",
"de": "",
"it": "",
"fr": ""
},
"redaction_recategorize": {
"en": "/en/index-en.html?contextId=redaction_recategorize",
"de": "",
"it": "",
"fr": ""
},
"redaction_hide": {
"en": "/en/index-en.html?contextId=redaction_hide",
"view": {
"en": "/en/index-en.html?contextId=view",
"de": "",
"it": "",
"fr": ""
@ -215,12 +89,6 @@
"it": "",
"fr": ""
},
"standard_view": {
"en": "/en/index-en.html?contextId=standard_view",
"de": "",
"it": "",
"fr": ""
},
"workload_filter": {
"en": "/en/index-en.html?contextId=workload_filter",
"de": "",
@ -233,18 +101,6 @@
"it": "",
"fr": ""
},
"delete_document_filter": {
"en": "/en/index-en.html?contextId=delete_document_filter",
"de": "",
"it": "",
"fr": ""
},
"delete_dossier_filter": {
"en": "",
"de": "",
"it": "",
"fr": ""
},
"edit_dossier_in_dossier": {
"en": "/en/index-en.html?contextId=edit_dossier_in_dossier",
"de": "",
@ -257,32 +113,8 @@
"it": "",
"fr": ""
},
"recommendation_resize": {
"en": "/en/index-en.html?contextId=recommendation_resize",
"de": "",
"it": "",
"fr": ""
},
"hint_resize": {
"en": "/en/index-en.html?contextId=hint_resize",
"de": "",
"it": "",
"fr": ""
},
"skipped_force_redaction": {
"en": "/en/index-en.html?contextId=skipped_force_redaction",
"de": "",
"it": "",
"fr": ""
},
"editor_document_features": {
"en": "/en/index-en.html?contextId=editor_document_features",
"de": "",
"it": "",
"fr": ""
},
"documents_quickfilter": {
"en": "/en/index-en.html?contextId=documents_quickfilter",
"document_features_in_editor": {
"en": "/en/index-en.html?contextId=document_features_in_editor",
"de": "",
"it": "",
"fr": ""
@ -305,14 +137,8 @@
"it": "",
"fr": ""
},
"dossiers_scroll_up_button": {
"en": "/en/index-en.html?contextId=dossiers_scroll_up_and_down",
"de": "",
"it": "",
"fr": ""
},
"dossiers_scroll_down_button": {
"en": "/en/index-en.html?contextId=dossiers_scroll_up_and_down",
"scroll_dossier_list": {
"en": "/en/index-en.html?contextId=scroll_dossier_list",
"de": "",
"it": "",
"fr": ""
@ -335,6 +161,18 @@
"it": "",
"fr": ""
},
"edit_dossier_delete_dossier": {
"en": "/en/index-en.html?contextId=edit_dossier_delete_dossier",
"de": "",
"it": "",
"fr": ""
},
"edit_dossier_archive_dossier": {
"en": "/en/index-en.html?contextId=edit_dossier_archive_dossier",
"de": "",
"it": "",
"fr": ""
},
"edit_dossier_download_package": {
"en": "/en/index-en.html?contextId=edit_dossier_download_package",
"de": "",
@ -389,26 +227,44 @@
"it": "",
"fr": ""
},
"redaction_remove_from_dictionary": {
"en": "/en/index-en.html?contextId=redaction_remove_from_dictionary",
"redaction_text": {
"en": "/en/index-en.html?contextId=redaction_text",
"de": "",
"it": "",
"fr": ""
},
"skipped_resize": {
"en": "/en/index-en.html?contextId=skipped_resize_redaction",
"recommendation": {
"en": "/en/index-en.html?contextId=recommendation",
"de": "",
"it": "",
"fr": ""
},
"skipped_recategorize": {
"en": "/en/index-en.html?contextId=skipped_recategorize_redaction",
"skipped": {
"en": "/en/index-en.html?contextId=skipped",
"de": "",
"it": "",
"fr": ""
},
"skipped_hide": {
"en": "/en/index-en.html?contextId=skipped_hide",
"hint_text": {
"en": "/en/index-en.html?contextId=hint_text",
"de": "",
"it": "",
"fr": ""
},
"hint_picture": {
"en": "/en/index-en.html?contextId=hint_picture",
"de": "",
"it": "",
"fr": ""
},
"picture": {
"en": "/en/index-en.html?contextId=picture",
"de": "",
"it": "",
"fr": ""
},
"image": {
"en": "/en/index-en.html?contextId=image",
"de": "",
"it": "",
"fr": ""

View File

@ -344,6 +344,7 @@
"skipped": "Übersprungen",
"suggestion-add": "Vorschlag für Schwärzung",
"suggestion-add-dictionary": "Vorschlag für neuen Wörterbucheintrag",
"suggestion-add-false-positive": "",
"suggestion-change-legal-basis": "Vorschlag für Änderung der Rechtsgrundlage",
"suggestion-force-hint": "",
"suggestion-force-redaction": "Vorschlag für erzwungene Schwärzung",
@ -1549,11 +1550,16 @@
},
"end-user-license-text": "Die Nutzung dieses Produkts unterliegt den Bedingungen der Endbenutzer-Lizenzvereinbarung für den RedactManager, sofern darin nichts anderweitig festgelegt.",
"end-user-license-title": "Endbenutzer-Lizenzvereinbarung",
"license-title": "",
"licensed-page-count": "Anzahl der lizenzierten Seiten",
"licensed-to": "Lizenziert für",
"licensing-details": "Lizenzdetails",
"licensing-period": "Laufzeit der Lizenz",
"ocr-analyzed-pages": "Mit OCR konvertierte Seiten",
"status": {
"active": "Aktiv",
"inactive": ""
},
"total-analyzed": "Seit {date} insgesamt analysierte Seiten",
"unlicensed-analyzed": "Über Lizenz hinaus analysierte Seiten",
"usage-details": "Nutzungsdetails"

View File

@ -344,6 +344,7 @@
"skipped": "Skipped",
"suggestion-add": "Suggested redaction",
"suggestion-add-dictionary": "Suggested dictionary add",
"suggestion-add-false-positive": "Suggested add to false positive",
"suggestion-change-legal-basis": "Suggested change legal basis",
"suggestion-force-hint": "Suggestion force hint",
"suggestion-force-redaction": "Suggestion force redaction",
@ -1549,11 +1550,16 @@
},
"end-user-license-text": "The use of this product is subject to the terms of the Redaction End User Agreement, unless otherwise specified therein.",
"end-user-license-title": "End User License Agreement",
"license-title": "License Title",
"licensed-page-count": "Number of licensed pages",
"licensed-to": "Licensed to",
"licensing-details": "Licensing Details",
"licensing-period": "Licensing Period",
"ocr-analyzed-pages": "OCR Analyzed Pages",
"status": {
"active": "Active",
"inactive": "Inactive"
},
"total-analyzed": "Total Analyzed Pages Since {date}",
"unlicensed-analyzed": "Unlicensed Analyzed Pages",
"usage-details": "Usage Details"

View File

@ -98,3 +98,7 @@
}
}
}
.align-center {
align-items: center;
}

View File

@ -6,7 +6,7 @@ server {
root /usr/share/nginx/html;
# SSL stuff for cloudflare proxy-ing - ignores SSL certificate and uses SNI
add_header Content-Security-Policy "default-src 'self'; script-src 'self' data: 'unsafe-eval'; script-src-elem 'self' data: blob: 'unsafe-inline'; script-src-attr 'self' data:; style-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self' data:";
add_header Content-Security-Policy "default-src 'self'; script-src 'self' blob: data: 'unsafe-eval' 'unsafe-inline'; script-src-elem 'self' data: blob: 'unsafe-inline'; script-src-attr 'self' data:; style-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self' data:";
proxy_ssl_verify off;
proxy_read_timeout 1m;

View File

@ -1,6 +1,6 @@
{
"name": "redaction",
"version": "3.451.0",
"version": "3.464.0",
"private": true,
"license": "MIT",
"scripts": {

View File

@ -11,31 +11,33 @@
.publication-icon {
background-color: variables.$red-1;
}
width: 280px;
}
.portal-contents {
.featured-content-label {
margin-top: 24px;
text-align: center;
}
.featured-content {
margin-top: 24px;
margin-bottom: 0;
.inner {
margin: 0;
display: grid;
grid-template-columns: 1fr 1fr;
grid-gap: 24px;
&::before {
content: none;
}
justify-content: center;
}
}
.publication-contents {
@include mixin.card;
}
.publication-contents {
@include mixin.card;
width: 250px;
margin: 0 15px !important;
@media only screen and (max-width: 768px) {
.portal-contents .inner {
grid-template-columns: 1fr;
li a {
font-size: inherit;
line-height: inherit;
}
}
}

Binary file not shown.

View File

@ -59,6 +59,7 @@
}
.portal-single-publication {
background-color: transparent;
width: 280px;
}
.portal-single-publication > a {
border-radius: 4px;
@ -67,78 +68,78 @@
background-color: #dd4d50;
}
.portal-contents {
.featured-content-label {
margin-top: 24px;
text-align: center;
}
.featured-content {
margin-top: 24px;
margin-bottom: 0;
}
.portal-contents .inner {
.featured-content .inner {
margin: 0;
display: grid;
grid-template-columns: 1fr 1fr;
grid-gap: 24px;
justify-content: center;
}
.portal-contents .inner::before {
content: none;
}
.publication-contents {
.featured-content .publication-contents {
padding: 24px 40px;
border: 1px solid #e2e4e9;
width: 100%;
margin: 0;
background-color: #fff;
border-radius: 4px;
width: 250px;
margin: 0 15px !important;
}
.publication-contents h4.featured-title,
.publication-contents .section-toc-title {
.featured-content .publication-contents h4.featured-title,
.featured-content .publication-contents .section-toc-title {
margin: 0;
}
.publication-contents h4.featured-title a,
.publication-contents .section-toc-title a {
.featured-content .publication-contents h4.featured-title a,
.featured-content .publication-contents .section-toc-title a {
color: #283241;
}
.publication-contents h4.featured-title a:hover,
.publication-contents .section-toc-title a:hover {
.featured-content .publication-contents h4.featured-title a:hover,
.featured-content .publication-contents .section-toc-title a:hover {
color: #283241;
text-decoration: underline;
}
.publication-contents .section-toc-title {
.featured-content .publication-contents .section-toc-title {
font-size: 28px;
font-weight: 300;
line-height: 36px;
}
.publication-contents ul {
.featured-content .publication-contents ul {
margin: 0;
padding: 0;
}
.publication-contents li {
.featured-content .publication-contents li {
margin: 4px 0;
}
.publication-contents li:first-child {
.featured-content .publication-contents li:first-child {
margin-top: 20px;
}
.publication-contents li:last-child {
.featured-content .publication-contents li:last-child {
margin-bottom: 40px;
}
.publication-contents li a {
.featured-content .publication-contents li a {
color: #dd4d50;
font-size: 16px;
line-height: 24px;
}
.publication-contents li a:hover {
.featured-content .publication-contents li a:hover {
color: #dd4d50;
text-decoration: underline;
}
.publication-contents h4 span,
.publication-contents li::before {
.featured-content .publication-contents h4 span,
.featured-content .publication-contents li::before {
display: none;
}
@media only screen and (max-width: 768px) {
.portal-contents .inner {
grid-template-columns: 1fr;
}
.featured-content .publication-contents li a {
font-size: inherit;
line-height: inherit;
}
/* Einleitung */
.cat-panel-1:before {
content: '\f277';