Browse Source

Fixes the unsplash editor. (#844)

* Fixes the unsplash editor.

* More improvements.
pull/845/head
Sebastian Stehle 4 years ago
committed by GitHub
parent
commit
716eeffdde
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      backend/i18n/frontend_en.json
  2. 2
      backend/i18n/frontend_it.json
  3. 2
      backend/i18n/frontend_nl.json
  4. 2
      backend/i18n/frontend_zh.json
  5. 2
      backend/i18n/source/frontend_en.json
  6. 75
      frontend/src/app/features/content/shared/forms/stock-photo-editor.component.html
  7. 61
      frontend/src/app/features/content/shared/forms/stock-photo-editor.component.scss
  8. 101
      frontend/src/app/features/content/shared/forms/stock-photo-editor.component.ts
  9. 51
      frontend/src/app/framework/angular/image-url.directive.ts
  10. 21
      frontend/src/app/framework/angular/list-view.component.scss
  11. 1
      frontend/src/app/framework/declarations.ts
  12. 4
      frontend/src/app/framework/module.ts
  13. 6
      frontend/src/app/shared/services/stock-photo.service.spec.ts
  14. 4
      frontend/src/app/shared/services/stock-photo.service.ts
  15. 4
      frontend/src/app/theme/_common.scss
  16. 16
      frontend/src/app/theme/_forms.scss

2
backend/i18n/frontend_en.json

@ -307,6 +307,7 @@
"common.lastExecuted": "Last Executed",
"common.latitudeShort": "Lat",
"common.loading": "Loading",
"common.loadMore": "Load More",
"common.logout": "Logout",
"common.logs": "Logs",
"common.longitudeShort": "Lon",
@ -490,6 +491,7 @@
"contents.statusQueries": "Status Queries",
"contents.stockPhotoEmpty": "Nothing selected",
"contents.stockPhotoSearch": "Search for Photos by Unsplash",
"contents.stockPhotoSearchEmpty": "Use the search bar above to find photos.",
"contents.tableHeaders.created": "Created",
"contents.tableHeaders.createdBy": "Created By",
"contents.tableHeaders.createdByShort": "By",

2
backend/i18n/frontend_it.json

@ -307,6 +307,7 @@
"common.lastExecuted": "Last Executed",
"common.latitudeShort": "Lat",
"common.loading": "Caricamento",
"common.loadMore": "Load More",
"common.logout": "Esci",
"common.logs": "Log",
"common.longitudeShort": "Lon",
@ -490,6 +491,7 @@
"contents.statusQueries": "Stato Query",
"contents.stockPhotoEmpty": "Nessuna selezione",
"contents.stockPhotoSearch": "Cerca foto su Unsplash",
"contents.stockPhotoSearchEmpty": "Use the search bar above to find photos.",
"contents.tableHeaders.created": "Creato",
"contents.tableHeaders.createdBy": "Creato da",
"contents.tableHeaders.createdByShort": "Da",

2
backend/i18n/frontend_nl.json

@ -307,6 +307,7 @@
"common.lastExecuted": "Laatst Uitgevoerd",
"common.latitudeShort": "Lat",
"common.loading": "Laden",
"common.loadMore": "Load More",
"common.logout": "Uitloggen",
"common.logs": "Logboeken",
"common.longitudeShort": "Lon",
@ -490,6 +491,7 @@
"contents.statusQueries": "Statusquery's",
"contents.stockPhotoEmpty": "Niets geselecteerd",
"contents.stockPhotoSearch": "Zoeken naar foto's op Unsplash",
"contents.stockPhotoSearchEmpty": "Use the search bar above to find photos.",
"contents.tableHeaders.created": "Gemaakt",
"contents.tableHeaders.createdBy": "Gemaakt door",
"contents.tableHeaders.createdByShort": "Door",

2
backend/i18n/frontend_zh.json

@ -307,6 +307,7 @@
"common.lastExecuted": "Last Executed",
"common.latitudeShort": "纬度",
"common.loading": "正在加载",
"common.loadMore": "Load More",
"common.logout": "注销",
"common.logs": "日志",
"common.longitudeShort": "Lon",
@ -490,6 +491,7 @@
"contents.statusQueries": "状态查询",
"contents.stockPhotoEmpty": "未选择任何内容",
"contents.stockPhotoSearch": "通过 Unsplash 搜索照片",
"contents.stockPhotoSearchEmpty": "Use the search bar above to find photos.",
"contents.tableHeaders.created": "创建",
"contents.tableHeaders.createdBy": "创建者",
"contents.tableHeaders.createdByShort": "By",

2
backend/i18n/source/frontend_en.json

@ -307,6 +307,7 @@
"common.lastExecuted": "Last Executed",
"common.latitudeShort": "Lat",
"common.loading": "Loading",
"common.loadMore": "Load More",
"common.logout": "Logout",
"common.logs": "Logs",
"common.longitudeShort": "Lon",
@ -490,6 +491,7 @@
"contents.statusQueries": "Status Queries",
"contents.stockPhotoEmpty": "Nothing selected",
"contents.stockPhotoSearch": "Search for Photos by Unsplash",
"contents.stockPhotoSearchEmpty": "Use the search bar above to find photos.",
"contents.tableHeaders.created": "Created",
"contents.tableHeaders.createdBy": "Created By",
"contents.tableHeaders.createdByShort": "By",

75
frontend/src/app/features/content/shared/forms/stock-photo-editor.component.html

@ -1,41 +1,52 @@
<div class="row g-0" (sqxResizeCondition)="setCompact($event)" [sqxResizeMinWidth]="600" [sqxResizeMaxWidth]="0">
<div class="col-auto col-image" [class.expand]="snapshot.isCompact">
<input class="form-control value" [formControl]="valueControl" readonly>
<button type="button" class="btn btn-text-secondary value-clear" (click)="reset()">
<i class="icon-close"></i>
</button>
<div class="input-group">
<button type="button" class="btn btn-outline-secondary" (click)="reset()" [disabled]="!valueControl.value">
<i class="icon-close"></i>
</button>
<div *ngIf="stockPhotoThumbnail | async; let url; else noThumb" class="preview">
<img [src]="url">
</div>
<button type="button" class="btn btn-outline-secondary" (click)="searchDialog.show()">
<i class="icon-search"></i>
</button>
<ng-template #noThumb>
<div class="preview preview-empty">
{{ 'contents.stockPhotoEmpty' | sqxTranslate }}
</div>
</ng-template>
</div>
<input class="form-control" [formControl]="valueControl" readonly>
</div>
<div *ngIf="stockPhotoThumbnail | async; let url;" class="preview mt-1" [class.hidden-important]="snapshot.thumbnailStatus === 'Failed'">
<img [src]="url" (error)="onThumbnailFailed()" (load)="onThumbnailLoaded()">
<div class="col ps-4" *ngIf="!snapshot.isCompact">
<i class="icon-angle-left icon"></i>
<i class="icon-spinner2 spin2" [class.hidden-important]="snapshot.thumbnailStatus === 'Loaded'"></i>
</div>
<input class="form-control" [formControl]="stockPhotoSearch" placeholder="{{ 'contents.stockPhotoSearch' | sqxTranslate }}">
<ng-container *sqxModal="searchDialog">
<sqx-modal-dialog size="lg" [fullHeight]="true" (close)="searchDialog.hide()">
<ng-container title>
<input class="form-control search" [formControl]="stockPhotoSearch" sqxFocusOnInit placeholder="{{ 'contents.stockPhotoSearch' | sqxTranslate }}">
<i *ngIf="snapshot.isLoading" class="icon-spinner2 spin2"></i>
</ng-container>
<sqx-list-view [isLoading]="snapshot.isLoading" [table]="true">
<ng-container content>
<div class="photos">
<ng-container *ngIf="stockPhotos | async; let photos; trackBy: trackByPhoto">
<div *ngFor="let photo of photos" class="photo" [class.selected]="isSelected(photo)" (click)="selectPhoto(photo)">
<img [src]="photo.thumbUrl">
<div class="photo-user">
<a class="photo-user-link" [href]="photo.userProfileUrl" sqxExternalLink sqxStopClick>
{{photo.user}}
</a>
</div>
<div *ngFor="let photo of snapshot.stockPhotos; trackBy: trackByPhoto" class="photo" [class.selected]="isSelected(photo)" (click)="selectPhoto(photo)">
<img [src]="photo.thumbUrl">
<div class="photo-user">
<a class="photo-user-link" [href]="photo.userProfileUrl" sqxExternalLink sqxStopClick>
{{photo.user}}
</a>
</div>
</ng-container>
</div>
</div>
<div class="empty small text-muted text-center" *ngIf="snapshot.stockPhotos.length === 0">
{{ 'contents.stockPhotoSearchEmpty' | sqxTranslate }}
</div>
<div class="mt-4 text-center" *ngIf="snapshot.hasMore">
<button class="btn btn-outline-secondary" type="button" (click)="loadMore()" [disabled]="snapshot.isLoading">
{{ 'common.loadMore' | sqxTranslate }} <i *ngIf="snapshot.isLoading" class="icon-spinner2 spin2"></i>
</button>
</div>
</sqx-list-view>
</div>
</div>
</ng-container>
</sqx-modal-dialog>
</ng-container>

61
frontend/src/app/features/content/shared/forms/stock-photo-editor.component.scss

@ -3,68 +3,45 @@
$color-user-background: rgba(0, 0, 0, 50%);
$color-background: #000;
$height: 300px;
.col-image {
@include force-width(400px);
&.expand {
@include force-width(100%);
}
img {
max-width: 100%;
}
}
.value {
padding-right: 2.5rem;
&-clear {
@include absolute(0, 0, auto, auto);
}
}
.preview {
@include force-height($height);
align-items: center;
background: $color-background;
border: 0;
border-radius: .25rem;
color: $color-white;
display: flex;
justify-content: center;
margin: 0;
margin-top: .5rem;
border-radius: 0;
color: white;
text-align: center;
&-empty {
padding: 1rem;
i {
margin-top: 1rem;
margin-bottom: 1rem;
}
img {
max-height: $height;
max-width: 100%;
}
}
sqx-list-view {
border: 1px solid $color-input;
border-radius: .25rem;
height: $height;
margin-top: .5rem;
.spin2 {
font-size: 14px;
}
.search {
display: inline-block;
margin-left: 0;
margin-right: 1rem;
width: 300px;
}
.icon {
@include absolute($height * .5, auto, auto, 5px);
color: $color-border;
font-size: 30px;
font-weight: lighter;
.empty {
@include absolute(40%, 0, null, 0);
}
.photos {
column-gap: 0;
column-width: 200px;
margin-left: -1rem;
margin-right: -1rem;
margin-left: 0;
}
.photo {

101
frontend/src/app/features/content/shared/forms/stock-photo-editor.component.ts

@ -7,9 +7,9 @@
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, forwardRef, Input, OnInit } from '@angular/core';
import { FormControl, NG_VALUE_ACCESSOR } from '@angular/forms';
import { of } from 'rxjs';
import { debounceTime, switchMap, tap } from 'rxjs/operators';
import { StatefulControlComponent, StockPhotoDto, StockPhotoService, thumbnail, Types, value$, valueProjection$ } from '@app/shared';
import { BehaviorSubject, of } from 'rxjs';
import { debounceTime, map, switchMap, tap } from 'rxjs/operators';
import { DialogModel, StatefulControlComponent, StockPhotoDto, StockPhotoService, thumbnail, Types, value$, valueProjection$ } from '@app/shared';
export const SQX_STOCK_PHOTO_EDITOR_CONTROL_VALUE_ACCESSOR: any = {
provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => StockPhotoEditorComponent), multi: true,
@ -19,10 +19,18 @@ interface State {
// True when loading assets.
isLoading?: boolean;
// True, when width less than 600 pixels.
isCompact?: boolean;
// The photos.
stockPhotos: ReadonlyArray<StockPhotoDto>;
// True if more photos are available.
hasMore?: boolean;
// The status of the thumbnail.
thumbnailStatus?: 'Loaded' | 'Failed';
}
type Request = { search?: string; page: number };
@Component({
selector: 'sqx-stock-photo-editor',
styleUrls: ['./stock-photo-editor.component.scss'],
@ -40,41 +48,64 @@ export class StockPhotoEditorComponent extends StatefulControlComponent<State, s
public valueControl = new FormControl('');
public stockPhotoThumbnail = valueProjection$(this.valueControl, x => thumbnail(x, 400) || x);
public stockPhotoRequests = new BehaviorSubject<Request>({ page: 1 });
public stockPhotoThumbnail = valueProjection$(this.valueControl, x => thumbnail(x, undefined, 300) || x);
public stockPhotoSearch = new FormControl('');
public stockPhotos =
value$(this.stockPhotoSearch).pipe(
debounceTime(500),
tap(query => {
if (query && query.length > 0) {
this.next({ isLoading: true });
}
}),
switchMap(query => {
if (query && query.length > 0) {
return this.stockPhotoService.getImages(query);
} else {
return of([]);
}
}),
tap(() => {
this.next({ isLoading: false });
}));
public searchDialog = new DialogModel();
constructor(changeDetector: ChangeDetectorRef,
private readonly stockPhotoService: StockPhotoService,
) {
super(changeDetector, {});
super(changeDetector, {
stockPhotos: [],
});
}
public ngOnInit() {
this.own(
value$(this.valueControl)
.subscribe(() => {
this.next({ thumbnailStatus: undefined });
}));
this.own(
value$(this.stockPhotoSearch)
.subscribe(search => {
this.stockPhotoRequests.next({ search, page: 1 });
}));
this.own(
this.valueControl.valueChanges
.subscribe(value => {
this.callChange(value);
this.callTouched();
}));
this.own(
this.stockPhotoRequests.pipe(
debounceTime(500),
tap(request => {
if (request.search && request.search.length > 0) {
this.next({ isLoading: true });
}
}),
switchMap(request => {
if (request.search && request.search.length > 0) {
return this.stockPhotoService.getImages(request.search, request.page).pipe(map(result => ({ request, result })));
} else {
return of(({ request, result: [] }));
}
}),
tap(({ request, result }) => {
this.next(s => ({
...s,
isLoading: false,
isDisabled: s.isDisabled,
stockPhotos: request.page > 1 ? [...s.stockPhotos, ...result] : result,
hasMore: result.length === 20,
}));
})));
}
public writeValue(obj: string) {
@ -93,13 +124,11 @@ export class StockPhotoEditorComponent extends StatefulControlComponent<State, s
}
}
public setCompact(isCompact: boolean) {
this.next({ isCompact });
}
public selectPhoto(photo: StockPhotoDto) {
if (!this.snapshot.isDisabled) {
this.valueControl.setValue(photo.url);
this.searchDialog.hide();
}
}
@ -109,6 +138,20 @@ export class StockPhotoEditorComponent extends StatefulControlComponent<State, s
}
}
public loadMore() {
const request = this.stockPhotoRequests.value;
this.stockPhotoRequests.next({ search: request.search, page: request.page + 1 });
}
public onThumbnailLoaded() {
this.next({ thumbnailStatus: 'Loaded' });
}
public onThumbnailFailed() {
this.next({ thumbnailStatus: 'Failed' });
}
public isSelected(photo: StockPhotoDto) {
return photo.url === this.valueControl.value;
}

51
frontend/src/app/framework/angular/image-url.directive.ts

@ -0,0 +1,51 @@
/*
* Squidex Headless CMS
*
* @license
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/
import { Directive, ElementRef, HostBinding, Input, NgZone, OnChanges, OnInit, Renderer2 } from '@angular/core';
import { ResourceOwner } from '@app/framework/internal';
@Directive({
selector: '[sqxImageUrl]',
})
export class ImageUrlDirective extends ResourceOwner implements OnChanges, OnInit {
@Input('sqxImageUrl') @HostBinding('attr.src')
public imageUrl!: string;
constructor(
private readonly zone: NgZone,
private readonly element: ElementRef,
private readonly renderer: Renderer2,
) {
super();
}
public ngOnInit() {
this.zone.runOutsideAngular(() => {
this.own(
this.renderer.listen(this.element.nativeElement, 'load', () => {
this.onLoad();
}));
this.own(
this.renderer.listen(this.element.nativeElement, 'error', () => {
this.onError();
}));
});
}
public ngOnChanges() {
this.onError();
}
public onLoad() {
this.renderer.setStyle(this.element.nativeElement, 'visibility', 'visible');
}
public onError() {
this.renderer.setStyle(this.element.nativeElement, 'visibility', 'hidden');
}
}

21
frontend/src/app/framework/angular/list-view.component.scss

@ -1,12 +1,20 @@
@import 'mixins';
@import 'vars';
%transition-opacity {
transition: opacity .2s ease;
}
%disabled {
pointer-events: none;
}
:host {
display: flex;
flex-direction: column;
flex-grow: 1;
overflow: hidden;
padding: 0;
position: relative;
}
.inner {
@ -20,10 +28,6 @@
}
}
%transition-opacity {
transition: opacity .2s ease;
}
.list {
&-content {
@extend %transition-opacity;
@ -96,16 +100,13 @@
overflow: visible;
}
%disabled {
pointer-events: none;
}
.loading-indicator {
@extend %disabled;
opacity: .5;
}
.loader {
@include absolute(20%, 0, auto, 0);
@include absolute(50%, 0, auto, 0);
@extend %disabled;
margin-top: -.5rem;
}

1
frontend/src/app/framework/declarations.ts

@ -43,6 +43,7 @@ export * from './angular/http/caching.interceptor';
export * from './angular/http/http-extensions';
export * from './angular/http/loading.interceptor';
export * from './angular/image-source.directive';
export * from './angular/image-url.directive';
export * from './angular/language-selector.component';
export * from './angular/layout-container.directive';
export * from './angular/layout.component';

4
frontend/src/app/framework/module.ts

@ -11,7 +11,7 @@ import { ModuleWithProviders, NgModule } from '@angular/core';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { RouterModule } from '@angular/router';
import { ColorPickerModule } from 'ngx-color-picker';
import { AnalyticsService, AutocompleteComponent, AvatarComponent, CachingInterceptor, CanDeactivateGuard, CheckboxGroupComponent, ClipboardService, CodeComponent, CodeEditorComponent, ColorPickerComponent, ConfirmClickDirective, ControlErrorsComponent, ControlErrorsMessagesComponent, CopyDirective, DarkenPipe, DatePipe, DateTimeEditorComponent, DayOfWeekPipe, DayPipe, DialogRendererComponent, DialogService, DisplayNamePipe, DropdownComponent, DropdownMenuComponent, DurationPipe, EditableTitleComponent, ExternalLinkDirective, FileDropDirective, FileSizePipe, FocusOnInitDirective, FormAlertComponent, FormErrorComponent, FormHintComponent, FromNowPipe, FullDateTimePipe, HighlightPipe, HoverBackgroundDirective, ImageSourceDirective, IndeterminateValueDirective, ISODatePipe, KeysPipe, KNumberPipe, LanguageSelectorComponent, LayoutComponent, LayoutContainerDirective, LightenPipe, ListViewComponent, LoadingInterceptor, LoadingService, LocalizedInputComponent, LocalStoreService, MarkdownDirective, MarkdownInlinePipe, MarkdownPipe, MessageBus, ModalDialogComponent, ModalDirective, ModalPlacementDirective, MonthPipe, OnboardingService, OnboardingTooltipComponent, PagerComponent, ParentLinkDirective, ProgressBarComponent, ResizedDirective, ResizeService, ResourceLoaderService, RootViewComponent, SafeHtmlPipe, SafeResourceUrlPipe, SafeUrlPipe, ScrollActiveDirective, ShortcutComponent, ShortcutDirective, ShortcutService, ShortDatePipe, ShortTimePipe, StarsComponent, StatusIconComponent, StopClickDirective, StopDragDirective, SyncScollingDirective, SyncWidthDirective, TabRouterlinkDirective, TagEditorComponent, TemplateWrapperDirective, TempService, TitleComponent, TitleService, ToggleComponent, ToolbarComponent, TooltipDirective, TransformInputDirective, TranslatePipe, VideoPlayerComponent } from './declarations';
import { AnalyticsService, AutocompleteComponent, AvatarComponent, CachingInterceptor, CanDeactivateGuard, CheckboxGroupComponent, ClipboardService, CodeComponent, CodeEditorComponent, ColorPickerComponent, ConfirmClickDirective, ControlErrorsComponent, ControlErrorsMessagesComponent, CopyDirective, DarkenPipe, DatePipe, DateTimeEditorComponent, DayOfWeekPipe, DayPipe, DialogRendererComponent, DialogService, DisplayNamePipe, DropdownComponent, DropdownMenuComponent, DurationPipe, EditableTitleComponent, ExternalLinkDirective, FileDropDirective, FileSizePipe, FocusOnInitDirective, FormAlertComponent, FormErrorComponent, FormHintComponent, FromNowPipe, FullDateTimePipe, HighlightPipe, HoverBackgroundDirective, ImageSourceDirective, ImageUrlDirective, IndeterminateValueDirective, ISODatePipe, KeysPipe, KNumberPipe, LanguageSelectorComponent, LayoutComponent, LayoutContainerDirective, LightenPipe, ListViewComponent, LoadingInterceptor, LoadingService, LocalizedInputComponent, LocalStoreService, MarkdownDirective, MarkdownInlinePipe, MarkdownPipe, MessageBus, ModalDialogComponent, ModalDirective, ModalPlacementDirective, MonthPipe, OnboardingService, OnboardingTooltipComponent, PagerComponent, ParentLinkDirective, ProgressBarComponent, ResizedDirective, ResizeService, ResourceLoaderService, RootViewComponent, SafeHtmlPipe, SafeResourceUrlPipe, SafeUrlPipe, ScrollActiveDirective, ShortcutComponent, ShortcutDirective, ShortcutService, ShortDatePipe, ShortTimePipe, StarsComponent, StatusIconComponent, StopClickDirective, StopDragDirective, SyncScollingDirective, SyncWidthDirective, TabRouterlinkDirective, TagEditorComponent, TemplateWrapperDirective, TempService, TitleComponent, TitleService, ToggleComponent, ToolbarComponent, TooltipDirective, TransformInputDirective, TranslatePipe, VideoPlayerComponent } from './declarations';
@NgModule({
imports: [
@ -55,6 +55,7 @@ import { AnalyticsService, AutocompleteComponent, AvatarComponent, CachingInterc
HighlightPipe,
HoverBackgroundDirective,
ImageSourceDirective,
ImageUrlDirective,
IndeterminateValueDirective,
ISODatePipe,
KeysPipe,
@ -138,6 +139,7 @@ import { AnalyticsService, AutocompleteComponent, AvatarComponent, CachingInterc
HighlightPipe,
HoverBackgroundDirective,
ImageSourceDirective,
ImageUrlDirective,
IndeterminateValueDirective,
ISODatePipe,
KeysPipe,

6
frontend/src/app/shared/services/stock-photo.service.spec.ts

@ -29,11 +29,11 @@ describe('StockPhotoService', () => {
inject([StockPhotoService, HttpTestingController], (stockPhotoService: StockPhotoService, httpMock: HttpTestingController) => {
let images: ReadonlyArray<StockPhotoDto>;
stockPhotoService.getImages('my-query').subscribe(result => {
stockPhotoService.getImages('my-query', 4).subscribe(result => {
images = result;
});
const req = httpMock.expectOne('https://stockphoto.squidex.io/?query=my-query&pageSize=100');
const req = httpMock.expectOne('https://stockphoto.squidex.io/?query=my-query&page=4');
expect(req.request.method).toEqual('GET');
expect(req.request.headers.get('If-Match')).toBeNull();
@ -64,7 +64,7 @@ describe('StockPhotoService', () => {
images = result;
});
const req = httpMock.expectOne('https://stockphoto.squidex.io/?query=my-query&pageSize=100');
const req = httpMock.expectOne('https://stockphoto.squidex.io/?query=my-query&page=1');
expect(req.request.method).toEqual('GET');
expect(req.request.headers.get('If-Match')).toBeNull();

4
frontend/src/app/shared/services/stock-photo.service.ts

@ -27,8 +27,8 @@ export class StockPhotoService {
) {
}
public getImages(query: string): Observable<ReadonlyArray<StockPhotoDto>> {
const url = `https://stockphoto.squidex.io/?query=${query}&pageSize=100`;
public getImages(query: string, page = 1): Observable<ReadonlyArray<StockPhotoDto>> {
const url = `https://stockphoto.squidex.io/?query=${query}&page=${page}`;
return this.http.get<any[]>(url).pipe(
map(body => {

4
frontend/src/app/theme/_common.scss

@ -175,6 +175,10 @@ hr {
display: none;
}
.hidden-important {
display: none !important;
}
// Hidden helper (fast *ngIf replacement)
.invisible {
visibility: hidden;

16
frontend/src/app/theme/_forms.scss

@ -21,6 +21,14 @@
}
}
}
&.preview {
background-color: $color-input;
border: 0;
border-radius: $border-radius;
opacity: .4;
pointer-events: none;
}
}
//
@ -134,14 +142,6 @@
}
.preview {
background-color: $color-input;
border: 0;
border-radius: $border-radius;
opacity: .4;
pointer-events: none;
}
//
// Control Dropdown item
//

Loading…
Cancel
Save