mirror of https://github.com/Squidex/squidex.git
52 changed files with 2033 additions and 1551 deletions
@ -0,0 +1,30 @@ |
|||
<ng-container *ngIf="isSmall; else large"> |
|||
<div class="rule-element-small" *ngIf="element" [style.background]="element.iconColor" [sqxHoverBackground]="element.iconColor | sqxDarken:5"> |
|||
<span class="rule-element-small-icon" [style.background]="element.iconColor | sqxDarken:5"> |
|||
<i class="svg-icon" [innerHtml]="element.iconImage | sqxSafeHtml"></i> |
|||
</span> |
|||
<span class="rule-element-small-text"> |
|||
{{element.display}} |
|||
</span> |
|||
</div> |
|||
</ng-container> |
|||
<ng-template #large> |
|||
<div class="row no-gutters rule-element-large"> |
|||
<div class="col col-auto"> |
|||
<div class="rule-element-large-icon" [style.background]="element.iconColor | sqxDarken:5"> |
|||
<div class="svg-icon" [innerHtml]="element.iconImage | sqxSafeHtml"></div> |
|||
</div> |
|||
</div> |
|||
<div class="col"> |
|||
<div class="rule-element-large-title">{{type}}</div> |
|||
|
|||
<div class="rule-element-large-text text-muted"> |
|||
{{element.description}} |
|||
</div> |
|||
|
|||
<div class="rule-element-large-link" *ngIf="element.readMore"> |
|||
<a [href]="element.readMore" target="_blank">Read More</a> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</ng-template> |
|||
@ -0,0 +1,76 @@ |
|||
@import '_vars'; |
|||
@import '_mixins'; |
|||
|
|||
.rule-element { |
|||
&-small { |
|||
& { |
|||
@include truncate; |
|||
@include transition(background-color .4s ease); |
|||
color: $color-dark-foreground; |
|||
cursor: pointer; |
|||
line-height: 2.8rem; |
|||
font-size: 1rem; |
|||
font-weight: normal; |
|||
padding-right: .8rem; |
|||
} |
|||
|
|||
&-icon { |
|||
height: 3rem; |
|||
display: inline-block; |
|||
line-height: 3rem; |
|||
font-size: 1.2rem; |
|||
font-weight: normal; |
|||
margin: 0; |
|||
margin-right: .5rem; |
|||
padding: 0 .8rem; |
|||
} |
|||
|
|||
.svg-icon { |
|||
display: inline-block; |
|||
min-width: 20px; |
|||
max-width: 20px; |
|||
min-height: 20px; |
|||
max-height: 20px; |
|||
fill: $color-dark-foreground; |
|||
} |
|||
} |
|||
|
|||
&-large { |
|||
& { |
|||
cursor: pointer; |
|||
min-height: 100px; |
|||
max-height: 100px; |
|||
} |
|||
|
|||
&-title { |
|||
font-weight: bold; |
|||
} |
|||
|
|||
&-link { |
|||
font-size: .8rem; |
|||
} |
|||
|
|||
&-text { |
|||
font-size: .8rem; |
|||
margin-top: .25rem; |
|||
margin-bottom: .25rem; |
|||
} |
|||
|
|||
&-icon { |
|||
display: inline-block; |
|||
margin-right: .5rem; |
|||
position: relative; |
|||
padding: 1rem; |
|||
line-height: 1px; |
|||
} |
|||
|
|||
.svg-icon { |
|||
display: inline-block; |
|||
min-width: 30px; |
|||
max-width: 30px; |
|||
min-height: 30px; |
|||
max-height: 30px; |
|||
fill: $color-dark-foreground; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,27 @@ |
|||
/* |
|||
* Squidex Headless CMS |
|||
* |
|||
* @license |
|||
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. |
|||
*/ |
|||
|
|||
import { ChangeDetectionStrategy, Component, Input } from '@angular/core'; |
|||
|
|||
import { RuleElementDto } from '@app/shared'; |
|||
|
|||
@Component({ |
|||
selector: 'sqx-rule-element', |
|||
styleUrls: ['./rule-element.component.scss'], |
|||
templateUrl: './rule-element.component.html', |
|||
changeDetection: ChangeDetectionStrategy.OnPush |
|||
}) |
|||
export class RuleElementComponent { |
|||
@Input() |
|||
public type: string; |
|||
|
|||
@Input() |
|||
public element: RuleElementDto; |
|||
|
|||
@Input() |
|||
public isSmall = true; |
|||
} |
|||
@ -0,0 +1,36 @@ |
|||
/* |
|||
* Squidex Headless CMS |
|||
* |
|||
* @license |
|||
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. |
|||
*/ |
|||
|
|||
import { Directive, ElementRef, HostListener, Input, Renderer2 } from '@angular/core'; |
|||
|
|||
@Directive({ |
|||
selector: '[sqxHoverBackground]' |
|||
}) |
|||
export class HoverBackgroundDirective { |
|||
private previousBackground: string | null; |
|||
|
|||
@Input('sqxHoverBackground') |
|||
public background: string; |
|||
|
|||
constructor( |
|||
private readonly element: ElementRef, |
|||
private readonly renderer: Renderer2 |
|||
) { |
|||
} |
|||
|
|||
@HostListener('mouseenter') |
|||
public onEnter() { |
|||
this.previousBackground = (<HTMLElement>this.element.nativeElement).style.background; |
|||
|
|||
this.renderer.setStyle(this.element.nativeElement, 'background', this.background); |
|||
} |
|||
|
|||
@HostListener('mouseleave') |
|||
public onLEave() { |
|||
this.renderer.setStyle(this.element.nativeElement, 'background', this.previousBackground); |
|||
} |
|||
} |
|||
@ -0,0 +1,64 @@ |
|||
/* |
|||
* Squidex Headless CMS |
|||
* |
|||
* @license |
|||
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. |
|||
*/ |
|||
|
|||
import { DarkenPipe, LightenPipe } from './colors.pipes'; |
|||
|
|||
describe('DarkenPipe', () => { |
|||
const pipe = new DarkenPipe(); |
|||
|
|||
it('should keep black unchanged', () => { |
|||
const result = pipe.transform('#000', 20); |
|||
|
|||
expect(result).toEqual('#000000'); |
|||
}); |
|||
|
|||
it('should darken rgb gray', () => { |
|||
const result = pipe.transform('rgb(100, 100, 100)', 20); |
|||
|
|||
expect(result).toEqual('#505050'); |
|||
}); |
|||
|
|||
it('should darken hey gray', () => { |
|||
const result = pipe.transform('#646464', 20); |
|||
|
|||
expect(result).toEqual('#505050'); |
|||
}); |
|||
|
|||
it('should darken mixed color', () => { |
|||
const result = pipe.transform('#FF91D1', 20); |
|||
|
|||
expect(result).toEqual('#cc74a7'); |
|||
}); |
|||
}); |
|||
|
|||
describe('LightenPipe', () => { |
|||
const pipe = new LightenPipe(); |
|||
|
|||
it('should keep white unchanged', () => { |
|||
const result = pipe.transform('#fff', 20); |
|||
|
|||
expect(result).toEqual('#ffffff'); |
|||
}); |
|||
|
|||
it('should lighten rgb gray', () => { |
|||
const result = pipe.transform('rgb(100, 100, 100)', 20); |
|||
|
|||
expect(result).toEqual('#787878'); |
|||
}); |
|||
|
|||
it('should lighten hey gray', () => { |
|||
const result = pipe.transform('#646464', 20); |
|||
|
|||
expect(result).toEqual('#787878'); |
|||
}); |
|||
|
|||
it('should lighten mixed color', () => { |
|||
const result = pipe.transform('#7F4868', 20); |
|||
|
|||
expect(result).toEqual('#98567d'); |
|||
}); |
|||
}); |
|||
@ -0,0 +1,164 @@ |
|||
/* |
|||
* Squidex Headless CMS |
|||
* |
|||
* @license |
|||
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. |
|||
*/ |
|||
|
|||
import { Pipe, PipeTransform } from '@angular/core'; |
|||
|
|||
interface RGBColor { |
|||
r: number; |
|||
g: number; |
|||
b: number; |
|||
} |
|||
|
|||
interface HSVColor { |
|||
h: number; |
|||
s: number; |
|||
v: number; |
|||
} |
|||
|
|||
interface IColorDefinition { |
|||
regex: RegExp; |
|||
|
|||
process(bots: RegExpExecArray): RGBColor; |
|||
} |
|||
|
|||
const ColorDefinitions: IColorDefinition[] = [ |
|||
{ |
|||
regex: /^rgb\((\d{1,3}),\s*(\d{1,3}),\s*(\d{1,3})\)$/, |
|||
process: (bits) => ({ |
|||
r: parseInt(bits[1], 10) / 255, |
|||
g: parseInt(bits[2], 10) / 255, |
|||
b: parseInt(bits[3], 10) / 255 |
|||
}) |
|||
}, |
|||
{ |
|||
regex: /^(\w{2})(\w{2})(\w{2})$/, |
|||
process: (bits) => ({ |
|||
r: parseInt(bits[1], 16) / 255, |
|||
g: parseInt(bits[2], 16) / 255, |
|||
b: parseInt(bits[3], 16) / 255 |
|||
}) |
|||
}, |
|||
{ |
|||
regex: /^(\w{1})(\w{1})(\w{1})$/, |
|||
process: (bits) => ({ |
|||
r: parseInt(bits[1] + bits[1], 16) / 255, |
|||
g: parseInt(bits[2] + bits[2], 16) / 255, |
|||
b: parseInt(bits[3] + bits[3], 16) / 255 |
|||
}) |
|||
} |
|||
]; |
|||
|
|||
function parseColor(value: string) { |
|||
if (value.charAt(0) === '#') { |
|||
value = value.substr(1, 6); |
|||
} |
|||
|
|||
value = value.replace(/ /g, '').toLowerCase(); |
|||
|
|||
for (let colorDefinition of ColorDefinitions) { |
|||
const bits = colorDefinition.regex.exec(value); |
|||
|
|||
if (bits) { |
|||
return colorDefinition.process(bits); |
|||
} |
|||
} |
|||
|
|||
throw new Error('Color is not in a valid format.'); |
|||
} |
|||
|
|||
function rgbToHsv({ r, g, b }: RGBColor): HSVColor { |
|||
const max = Math.max(r, g, b); |
|||
const min = Math.min(r, g, b); |
|||
|
|||
let h = 0; |
|||
let d = max - min; |
|||
let s = max === 0 ? 0 : d / max; |
|||
let v = max; |
|||
|
|||
if (max === min) { |
|||
h = 0; |
|||
} else { |
|||
switch (max) { |
|||
case r: h = (g - b) / d + (g < b ? 6 : 0); break; |
|||
case g: h = (b - r) / d + 2; break; |
|||
case b: h = (r - g) / d + 4; break; |
|||
} |
|||
|
|||
h /= 6; |
|||
} |
|||
|
|||
return { h, s, v }; |
|||
} |
|||
|
|||
function hsvToRgb({ h, s, v }: HSVColor): RGBColor { |
|||
let r = 0, g = 0, b = 0; |
|||
|
|||
let i = Math.floor(h * 6); |
|||
let f = h * 6 - i; |
|||
let p = v * (1 - s); |
|||
let q = v * (1 - f * s); |
|||
let t = v * (1 - (1 - f) * s); |
|||
|
|||
switch (i % 6) { |
|||
case 0: r = v, g = t, b = p; break; |
|||
case 1: r = q, g = v, b = p; break; |
|||
case 2: r = p, g = v, b = t; break; |
|||
case 3: r = p, g = q, b = v; break; |
|||
case 4: r = t, g = p, b = v; break; |
|||
case 5: r = v, g = p, b = q; break; |
|||
} |
|||
|
|||
return { r, g, b }; |
|||
} |
|||
|
|||
function colorString({ r, g, b }: RGBColor) { |
|||
let rs = Math.round(r * 255).toString(16); |
|||
let gs = Math.round(g * 255).toString(16); |
|||
let bs = Math.round(b * 255).toString(16); |
|||
|
|||
if (rs.length === 1) { |
|||
rs = '0' + rs; |
|||
} |
|||
if (gs.length === 1) { |
|||
gs = '0' + gs; |
|||
} |
|||
if (bs.length === 1) { |
|||
bs = '0' + bs; |
|||
} |
|||
|
|||
return '#' + rs + gs + bs; |
|||
} |
|||
|
|||
@Pipe({ |
|||
name: 'sqxDarken', |
|||
pure: true |
|||
}) |
|||
export class DarkenPipe implements PipeTransform { |
|||
public transform(value: string, percentage: number): any { |
|||
const rgb = parseColor(value); |
|||
const hsv = rgbToHsv(rgb); |
|||
|
|||
hsv.v = Math.max(0, hsv.v * (1 - (percentage / 100))); |
|||
|
|||
return colorString(hsvToRgb(hsv)); |
|||
} |
|||
} |
|||
|
|||
@Pipe({ |
|||
name: 'sqxLighten', |
|||
pure: true |
|||
}) |
|||
export class LightenPipe implements PipeTransform { |
|||
public transform(value: string, percentage: number): any { |
|||
const rgb = parseColor(value); |
|||
const hsv = rgbToHsv(rgb); |
|||
|
|||
hsv.v = Math.min(1, hsv.v * (1 + (percentage / 100))); |
|||
|
|||
return colorString(hsvToRgb(hsv)); |
|||
} |
|||
} |
|||
@ -0,0 +1,24 @@ |
|||
/* |
|||
* Squidex Headless CMS |
|||
* |
|||
* @license |
|||
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. |
|||
*/ |
|||
|
|||
import { Pipe, PipeTransform } from '@angular/core'; |
|||
import { DomSanitizer, SafeHtml } from '@angular/platform-browser'; |
|||
|
|||
@Pipe({ |
|||
name: 'sqxSafeHtml', |
|||
pure: true |
|||
}) |
|||
export class SafeHtmlPipe implements PipeTransform { |
|||
constructor( |
|||
public readonly domSanitizer: DomSanitizer |
|||
) { |
|||
} |
|||
|
|||
public transform(html: string): SafeHtml { |
|||
return this.domSanitizer.bypassSecurityTrustHtml(html); |
|||
} |
|||
} |
|||
@ -1,98 +0,0 @@ |
|||
@import '_mixins'; |
|||
@import '_vars'; |
|||
|
|||
$trigger-asset-changed: #3389ff; |
|||
$trigger-content-changed: #3389ff; |
|||
|
|||
$action-webhook: #4bb958; |
|||
$action-elasticsearch: #1e5470; |
|||
$action-algolia: #0d9bf9; |
|||
$action-slack: #5c3a58; |
|||
$action-azure: #55b3ff; |
|||
$action-fastly: #e23335; |
|||
$action-medium: #00ab6c; |
|||
$action-twitter: #1da1f2; |
|||
|
|||
// sass-lint:disable class-name-format |
|||
|
|||
@mixin build-element($color) { |
|||
& { |
|||
background: $color; |
|||
} |
|||
|
|||
&:hover { |
|||
background: darken($color, 5%); |
|||
} |
|||
|
|||
.rule-element-icon { |
|||
background: darken($color, 5%); |
|||
} |
|||
} |
|||
|
|||
.wizard-title { |
|||
margin-bottom: 1rem; |
|||
} |
|||
|
|||
.rule-element { |
|||
& { |
|||
@include truncate; |
|||
@include transition(background-color .4s ease); |
|||
color: $color-dark-foreground; |
|||
cursor: pointer; |
|||
line-height: 2.8rem; |
|||
font-size: 1rem; |
|||
font-weight: normal; |
|||
padding-right: .8rem; |
|||
} |
|||
|
|||
&-icon { |
|||
height: 3rem; |
|||
display: inline-block; |
|||
line-height: 3rem; |
|||
font-size: 1.2rem; |
|||
font-weight: normal; |
|||
margin: 0; |
|||
margin-right: .5rem; |
|||
padding: 0 .8rem; |
|||
} |
|||
} |
|||
|
|||
.rule-element-AssetChanged { |
|||
@include build-element($trigger-asset-changed); |
|||
} |
|||
|
|||
.rule-element-ContentChanged { |
|||
@include build-element($trigger-content-changed); |
|||
} |
|||
|
|||
.rule-element-AzureQueue { |
|||
@include build-element($action-azure); |
|||
} |
|||
|
|||
.rule-element-Algolia { |
|||
@include build-element($action-algolia); |
|||
} |
|||
|
|||
.rule-element-ElasticSearch { |
|||
@include build-element($action-elasticsearch); |
|||
} |
|||
|
|||
.rule-element-Fastly { |
|||
@include build-element($action-fastly); |
|||
} |
|||
|
|||
.rule-element-Medium { |
|||
@include build-element($action-medium); |
|||
} |
|||
|
|||
.rule-element-Slack { |
|||
@include build-element($action-slack); |
|||
} |
|||
|
|||
.rule-element-Tweet { |
|||
@include build-element($action-twitter); |
|||
} |
|||
|
|||
.rule-element-Webhook { |
|||
@include build-element($action-webhook); |
|||
} |
|||
File diff suppressed because it is too large
Binary file not shown.
|
Before Width: | Height: | Size: 82 KiB After Width: | Height: | Size: 76 KiB |
Binary file not shown.
Binary file not shown.
File diff suppressed because it is too large
Loading…
Reference in new issue