Browse Source

Image editing

pull/479/head
Sebastian 6 years ago
parent
commit
df46cc134c
  1. 2
      frontend/app/features/apps/pages/news-dialog.component.html
  2. 2
      frontend/app/features/content/shared/references/content-selector.component.html
  3. 2
      frontend/app/features/rules/pages/rules/rule-wizard.component.html
  4. 2
      frontend/app/features/schemas/pages/schema/fields/field-wizard.component.html
  5. 2
      frontend/app/features/schemas/pages/schemas/schema-form.component.html
  6. 2
      frontend/app/features/settings/pages/clients/client-connect-form.component.html
  7. 2
      frontend/app/framework/angular/modals/modal-dialog.component.html
  8. 2
      frontend/app/framework/angular/modals/modal-dialog.component.ts
  9. 11
      frontend/app/framework/services/resource-loader.service.ts
  10. 158
      frontend/app/shared/components/assets/asset-dialog.component.html
  11. 12
      frontend/app/shared/components/assets/asset-dialog.component.scss
  12. 7
      frontend/app/shared/components/assets/asset-dialog.component.ts
  13. 2
      frontend/app/shared/components/assets/assets-selector.component.html
  14. 1
      frontend/app/shared/components/assets/image-editor.component.html
  15. 29
      frontend/app/shared/components/assets/image-editor.component.scss
  16. 159
      frontend/app/shared/components/assets/image-editor.component.ts
  17. 2
      frontend/app/shared/components/search/search-form.component.html
  18. 1
      frontend/app/shared/declarations.ts
  19. 2
      frontend/app/shared/module.ts
  20. 4
      frontend/app/theme/_bootstrap.scss
  21. 4
      frontend/app/theme/_mixins.scss

2
frontend/app/features/apps/pages/news-dialog.component.html

@ -1,4 +1,4 @@
<sqx-modal-dialog large="true" (close)="close.emit()">
<sqx-modal-dialog size="lg" (close)="close.emit()">
<ng-container title>
New Features
</ng-container>

2
frontend/app/features/content/shared/references/content-selector.component.html

@ -1,4 +1,4 @@
<sqx-modal-dialog (close)="emitComplete()" large="true" fullHeight="true" grid="true" flexBody="true">
<sqx-modal-dialog (close)="emitComplete()" size="lg" fullHeight="true" grid="true" flexBody="true">
<ng-container title>
<div class="row">
<div class="col-selector">

2
frontend/app/features/rules/pages/rules/rule-wizard.component.html

@ -1,4 +1,4 @@
<sqx-modal-dialog large="true" fullHeight="true" (close)="emitComplete()" [showFooter]="step === 2 || step === 4">
<sqx-modal-dialog size="lg" fullHeight="true" (close)="emitComplete()" [showFooter]="step === 2 || step === 4">
<ng-container title>
<ng-container *ngIf="mode === 'EditTrigger'">
Edit Trigger

2
frontend/app/features/schemas/pages/schema/fields/field-wizard.component.html

@ -1,4 +1,4 @@
<sqx-modal-dialog (close)="emitComplete()" large="true">
<sqx-modal-dialog (close)="emitComplete()" size="lg">
<ng-container title>
<ng-container *ngIf="parent; else noParent">
Add Nested Field

2
frontend/app/features/schemas/pages/schemas/schema-form.component.html

@ -1,5 +1,5 @@
<form [formGroup]="createForm.form" (ngSubmit)="createSchema()">
<sqx-modal-dialog (close)="cancel.emit()" [large]="false">
<sqx-modal-dialog (close)="cancel.emit()">
<ng-container title>
<ng-container *ngIf="import; else noImport">
Clone Schema

2
frontend/app/features/settings/pages/clients/client-connect-form.component.html

@ -1,4 +1,4 @@
<sqx-modal-dialog (close)="complete.emit()" large="true">
<sqx-modal-dialog (close)="complete.emit()" size="lg" >
<ng-container title>
Connect
</ng-container>

2
frontend/app/framework/angular/modals/modal-dialog.component.html

@ -1,5 +1,5 @@
<div class="modal" @fade>
<div class="modal-dialog" [class.modal-lg]="large" [class.modal-fh]="fullHeight">
<div class="modal-dialog modal-{{size}}" [class.modal-fh]="fullHeight">
<div class="modal-content">
<div class="modal-header" *ngIf="showHeader">
<h4 class="modal-title">

2
frontend/app/framework/angular/modals/modal-dialog.component.ts

@ -35,7 +35,7 @@ export class ModalDialogComponent implements AfterViewInit {
public showTabs = true;
@Input()
public large = false;
public size: 'sm' | 'md' | 'lg' | 'xl' = 'md';
@Input()
public flexBody = false;

11
frontend/app/framework/services/resource-loader.service.ts

@ -44,13 +44,14 @@ export class ResourceLoaderService {
let result = this.cache[key];
if (!result) {
result = new Promise((resolve, reject) => {
const script = this.renderer.createElement('script');
const script = this.renderer.createElement('script');
this.renderer.setProperty(script, 'src', url);
this.renderer.setProperty(script, 'async', false);
this.renderer.appendChild(document.body, script);
result = new Promise((resolve) => {
this.renderer.listen(script, 'load', () => resolve());
this.renderer.setProperty(script, 'src', url);
this.renderer.setProperty(script, 'async', true);
this.renderer.appendChild(document.body, script);
});
this.cache[key] = result;

158
frontend/app/shared/components/assets/asset-dialog.component.html

@ -1,84 +1,100 @@
<form [formGroup]="annotateForm.form" (ngSubmit)="annotateAsset()">
<sqx-modal-dialog (close)="emitComplete()">
<sqx-modal-dialog (close)="emitComplete()" size="xl" fullHeight="true" [showHeader]="true" [showFooter]="false">
<ng-container title>
Update Asset
</ng-container>
<ng-container tabs>
<ul class="nav nav-tabs2">
<li class="nav-item" *ngFor="let tab of selectableTabs">
<a class="nav-link" [class.active]="tab === selectedTab" (click)="selectTab(tab)">{{tab}}</a>
</li>
</ul>
<button type="submit" class="float-right btn btn-primary" [disabled]="!isEditable">Save</button>
</ng-container>
<ng-container content>
<sqx-form-error [error]="annotateForm.error | async"></sqx-form-error>
<div class="form-group">
<label for="fileName">Name</label>
<ng-container [ngSwitch]="selectedTab">
<ng-container *ngSwitchCase="'Image'">
<div class="image">
<sqx-image-editor [imageUrl]="asset.contentUrl"></sqx-image-editor>
</div>
</ng-container>
<ng-container *ngSwitchCase="'Metadata'">
<div class="metadata">
<sqx-form-error [error]="annotateForm.error | async"></sqx-form-error>
<div class="form-group">
<label for="fileName">Name</label>
<sqx-control-errors for="fileName" [submitted]="annotateForm.submitted | async"></sqx-control-errors>
<input type="text" class="form-control" id="fileName" formControlName="fileName" />
</div>
<div class="form-group">
<label for="slug">Slug</label>
<sqx-control-errors for="slug" [submitted]="annotateForm.submitted | async"></sqx-control-errors>
<input type="text" class="form-control slug" id="slug" formControlName="slug" sqxTransformInput="Slugify" />
<button type="button" class="btn btn-text-secondary btn-sm slug-generate" (click)="generateSlug()">
Generate
</button>
</div>
<div class="form-group">
<label>Tags</label>
<sqx-control-errors for="tags" [submitted]="annotateForm.submitted | async"></sqx-control-errors>
<sqx-tag-editor [suggestions]="allTags" [allowDuplicates]="false" [undefinedWhenEmpty]="false" formControlName="tags"></sqx-tag-editor>
</div>
<div class="form-group">
<label>Metadata</label>
<div class="form-group row no-gutters" *ngFor="let form of annotateForm.metadata.controls; let i = index">
<div class="col col-name pr-1">
<sqx-control-errors [for]="form.get('name')" fieldName="Name" [submitted]="annotateForm.submitted | async"></sqx-control-errors>
<sqx-control-errors for="fileName" [submitted]="annotateForm.submitted | async"></sqx-control-errors>
<input type="text" class="form-control" id="fileName" formControlName="fileName" />
</div>
<div class="form-group">
<label for="slug">Slug</label>
<input type="text" class="form-control" maxlength="1000" [formControl]="form.get('name')" placeholder="Name" />
</div>
<sqx-control-errors for="slug" [submitted]="annotateForm.submitted | async"></sqx-control-errors>
<input type="text" class="form-control slug" id="slug" formControlName="slug" sqxTransformInput="Slugify" />
<button type="button" class="btn btn-text-secondary btn-sm btn-generate" (click)="generateSlug()">
Generate
</button>
</div>
<div class="form-group">
<label>Tags</label>
<div class="col pr-1">
<sqx-control-errors [for]="form.get('value')" fieldName="Value" [submitted]="annotateForm.submitted | async"></sqx-control-errors>
<sqx-control-errors for="tags" [submitted]="annotateForm.submitted | async"></sqx-control-errors>
<sqx-tag-editor [suggestions]="allTags" [allowDuplicates]="false" [undefinedWhenEmpty]="false" formControlName="tags"></sqx-tag-editor>
</div>
<div class="form-group">
<label>Metadata</label>
<div class="form-group row no-gutters" *ngFor="let form of annotateForm.metadata.controls; let i = index">
<div class="col col-name pr-1">
<sqx-control-errors [for]="form.get('name')" fieldName="Name" [submitted]="annotateForm.submitted | async"></sqx-control-errors>
<input type="text" class="form-control" maxlength="1000" [formControl]="form.get('name')" placeholder="Name" />
<input type="text" class="form-control" maxlength="1000" [formControl]="form.get('value')" placeholder="Value" />
</div>
<div class="col-auto col-options">
<button type="button" class="btn btn-text-danger"
[disabled]="!isEditable"
(sqxConfirmClick)="annotateForm.removeMetadata(i)"
confirmTitle="Remove url"
confirmText="Do you really want to remove this metadata?">
<i class="icon-bin2"></i>
</button>
</div>
</div>
<div class="form-group">
<button type="button" class="btn btn-success" (click)="annotateForm.addMetadata()" [disabled]="!isEditable">
Add Metadata
</button>
</div>
</div>
<div class="form-group form-check">
<input type="checkbox" class="form-check-input" id="isProtected" formControlName="isProtected" />
<label class="form-check-label" for="isProtected">Protected</label>
</div>
</div>
<div class="col pr-1">
<sqx-control-errors [for]="form.get('value')" fieldName="Value" [submitted]="annotateForm.submitted | async"></sqx-control-errors>
<input type="text" class="form-control" maxlength="1000" [formControl]="form.get('value')" placeholder="Value" />
</div>
<div class="col-auto col-options">
<button type="button" class="btn btn-text-danger"
[disabled]="!isEditable"
(sqxConfirmClick)="annotateForm.removeMetadata(i)"
confirmTitle="Remove url"
confirmText="Do you really want to remove this metadata?">
<i class="icon-bin2"></i>
</button>
</div>
</div>
<div class="form-group">
<button type="button" class="btn btn-success" (click)="annotateForm.addMetadata()" [disabled]="!isEditable">
Add Metadata
</button>
</div>
</div>
<div class="form-group form-check">
<input type="checkbox" class="form-check-input" id="isProtected" formControlName="isProtected" />
<label class="form-check-label" for="isProtected">Protected</label>
</div>
</ng-container>
<ng-container footer>
<button type="reset" class="float-left btn btn-secondary" (click)="emitComplete()">Cancel</button>
<button type="submit" class="float-right btn btn-primary" *ngIf="isEditable">Save</button>
</ng-container>
</ng-container>
</ng-container>
</sqx-modal-dialog>
</form>

12
frontend/app/shared/components/assets/asset-dialog.component.scss

@ -2,11 +2,21 @@
position: relative;
}
.image {
@include absolute(0, 0, 0, 0);
}
.metadata {
margin: 1rem auto;
max-width: 500px;
min-width: 0;
}
.slug {
padding-right: 6rem;
}
.btn-generate {
.slug-generate {
& {
@include absolute(auto, 10px, 3px, auto);
}

7
frontend/app/shared/components/assets/asset-dialog.component.ts

@ -32,6 +32,9 @@ export class AssetDialogComponent implements OnInit {
public isEditable = false;
public selectableTabs: ReadonlyArray<string> = ['Image', 'Metadata'];
public selectedTab = this.selectableTabs[0];
public annotateForm = new AnnotateAssetForm(this.formBuilder);
constructor(
@ -47,6 +50,10 @@ export class AssetDialogComponent implements OnInit {
this.annotateForm.setEnabled(this.isEditable);
}
public selectTab(tab: string) {
this.selectedTab = tab;
}
public generateSlug() {
this.annotateForm.generateSlug(this.asset);
}

2
frontend/app/shared/components/assets/assets-selector.component.html

@ -1,4 +1,4 @@
<sqx-modal-dialog (close)="emitComplete()" large="true" fullHeight="true">
<sqx-modal-dialog (close)="emitComplete()" size="lg" fullHeight="true">
<ng-container title>
Select assets
</ng-container>

1
frontend/app/shared/components/assets/image-editor.component.html

@ -0,0 +1 @@
<div #editor></div>

29
frontend/app/shared/components/assets/image-editor.component.scss

@ -0,0 +1,29 @@
:host {
height: 100%;
}
:host /deep/ {
.tui-image-editor-header {
display: none;
}
* {
box-sizing: content-box;
}
.tie-btn-delete {
display: none !important;
}
.tie-btn-delete-all {
display: none !important;
& + li {
display: none !important;
}
}
svg {
vertical-align: baseline;
}
}

159
frontend/app/shared/components/assets/image-editor.component.ts

@ -0,0 +1,159 @@
/*
* Squidex Headless CMS
*
* @license
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/
import { AfterViewInit, ChangeDetectionStrategy, Component, ElementRef, Input, OnChanges, ViewChild } from '@angular/core';
import { ResourceLoaderService } from '@app/shared/internal';
declare var tui: any;
const blackTheme = {
'common.bi.image': 'https://uicdn.toast.com/toastui/img/tui-image-editor-bi.png',
'common.bisize.width': '251px',
'common.bisize.height': '21px',
'common.backgroundImage': 'none',
'common.backgroundColor': '#000',
'common.border': '0px',
// header
'header.backgroundImage': 'none',
'header.backgroundColor': 'transparent',
'header.border': '0px',
// load button
'loadButton.backgroundColor': '#fff',
'loadButton.border': '1px solid #ddd',
'loadButton.color': '#222',
'loadButton.fontFamily': '\'Noto Sans\', sans-serif',
'loadButton.fontSize': '12px',
// download button
'downloadButton.backgroundColor': '#fdba3b',
'downloadButton.border': '1px solid #fdba3b',
'downloadButton.color': '#fff',
'downloadButton.fontFamily': '\'Noto Sans\', sans-serif',
'downloadButton.fontSize': '12px',
// main icons
'menu.normalIcon.path': 'https://unpkg.com/tui-image-editor@3.7.3/dist/svg/icon-d.svg',
'menu.normalIcon.name': 'icon-d',
'menu.activeIcon.path': 'https://unpkg.com/tui-image-editor@3.7.3/dist/svg/icon-b.svg',
'menu.activeIcon.name': 'icon-b',
'menu.disabledIcon.path': 'https://unpkg.com/tui-image-editor@3.7.3/dist/svg/icon-a.svg',
'menu.disabledIcon.name': 'icon-a',
'menu.hoverIcon.path': 'https://unpkg.com/tui-image-editor@3.7.3/dist/svg/icon-c.svg',
'menu.hoverIcon.name': 'icon-c',
'menu.iconSize.width': '24px',
'menu.iconSize.height': '24px',
// submenu primary color
'submenu.backgroundColor': '#000',
'submenu.partition.color': '#3c3c3c',
// submenu icons
'submenu.normalIcon.path': 'https://unpkg.com/tui-image-editor@3.7.3/dist/svg/icon-d.svg',
'submenu.normalIcon.name': 'icon-d',
'submenu.activeIcon.path': 'https://unpkg.com/tui-image-editor@3.7.3/dist/svg/icon-c.svg',
'submenu.activeIcon.name': 'icon-c',
'submenu.iconSize.width': '32px',
'submenu.iconSize.height': '32px',
// submenu labels
'submenu.normalLabel.color': '#8a8a8a',
'submenu.normalLabel.fontWeight': 'normal',
'submenu.activeLabel.color': '#fff',
'submenu.activeLabel.fontWeight': 'normal',
// checkbox style
'checkbox.border': '0px',
'checkbox.backgroundColor': '#fff',
// range style
'range.pointer.color': '#fff',
'range.bar.color': '#666',
'range.subbar.color': '#d1d1d1',
'range.disabledPointer.color': '#414141',
'range.disabledBar.color': '#282828',
'range.disabledSubbar.color': '#414141',
'range.value.color': '#fff',
'range.value.fontWeight': 'normal',
'range.value.fontSize': '11px',
'range.value.border': '1px solid #353535',
'range.value.backgroundColor': '#151515',
'range.title.color': '#fff',
'range.title.fontWeight': 'normal',
// colorpicker style
'colorpicker.button.border': '1px solid #1e1e1e',
'colorpicker.title.color': '#fff'
};
@Component({
selector: 'sqx-image-editor',
styleUrls: ['./image-editor.component.scss'],
templateUrl: './image-editor.component.html',
changeDetection: ChangeDetectionStrategy.OnPush
})
export class ImageEditorComponent implements AfterViewInit, OnChanges {
private imageEditor: any;
@Input()
public imageUrl: string;
@ViewChild('editor', { static: false })
public editor: ElementRef;
constructor(
private readonly resourceLoader: ResourceLoaderService
) {
}
public ngOnChanges() {
if (this.imageEditor && this.imageUrl) {
this.imageEditor.loadImageFromURL(this.imageUrl);
}
}
public ngAfterViewInit() {
const styles = [
'https://uicdn.toast.com/tui-color-picker/latest/tui-color-picker.css',
'https://uicdn.toast.com/tui-image-editor/latest/tui-image-editor.css'
];
const scripts = [
'https://cdnjs.cloudflare.com/ajax/libs/jquery/1.8.3/jquery.min.js',
'https://cdnjs.cloudflare.com/ajax/libs/fabric.js/3.3.2/fabric.js',
'https://uicdn.toast.com/tui.code-snippet/latest/tui-code-snippet.min.js',
'https://uicdn.toast.com/tui-color-picker/latest/tui-color-picker.js',
'https://uicdn.toast.com/tui-image-editor/latest/tui-image-editor.js'
];
styles.forEach(style => this.resourceLoader.loadStyle(style));
const s = scripts.map(script => this.resourceLoader.loadScript(script));
Promise.all(s).then(() => {
this.imageEditor = new tui.ImageEditor(this.editor.nativeElement, {
includeUI: {
loadImage: {
path: this.imageUrl, name: 'image'
},
menu: [
'crop',
'flip',
'rotate',
'mask',
'filter'
],
theme: blackTheme
}
});
});
}
}

2
frontend/app/shared/components/search/search-form.component.html

@ -41,7 +41,7 @@
</sqx-onboarding-tooltip>
<ng-container *sqxModal="searchDialog">
<sqx-modal-dialog (close)="searchDialog.hide()" large="true">
<sqx-modal-dialog (close)="searchDialog.hide()" size="lg">
<ng-container title>
Custom Query
</ng-container>

1
frontend/app/shared/declarations.ts

@ -18,6 +18,7 @@ export * from './components/assets/asset-uploader.component';
export * from './components/assets/asset.component';
export * from './components/assets/assets-list.component';
export * from './components/assets/assets-selector.component';
export * from './components/assets/image-editor.component';
export * from './components/assets/pipes';
export * from './components/comments/comment.component';

2
frontend/app/shared/module.ts

@ -61,6 +61,7 @@ import {
HistoryListComponent,
HistoryMessagePipe,
HistoryService,
ImageEditorComponent,
LanguageSelectorComponent,
LanguagesService,
LanguagesState,
@ -146,6 +147,7 @@ import {
HistoryComponent,
HistoryListComponent,
HistoryMessagePipe,
ImageEditorComponent,
LanguageSelectorComponent,
MarkdownEditorComponent,
QueryComponent,

4
frontend/app/theme/_bootstrap.scss

@ -675,9 +675,9 @@ $icon-size: 4.5rem;
}
&-content {
@include box-shadow(0, 6px, 16px, .4);
@include box-shadow2(0, 8px, 16px, .3);
border: 0;
border-radius: .4rem, .35rem, .35rem, .4rem;
border-radius: .4rem .35rem .35rem .4rem;
max-height: 100%;
}

4
frontend/app/theme/_mixins.scss

@ -170,6 +170,10 @@
box-shadow: $x-axis $y-axis $blur rgba(0, 0, 0, $alpha);
}
@mixin box-shadow2($x-axis: 0, $y-axis: 1px, $blur: 2px, $alpha: .1) {
box-shadow: $x-axis $y-axis $blur rgba(0, 0, 0, $alpha);
}
@mixin box-shadow-inner($x-axis: 0, $y-axis: 1px, $blur: 2px, $alpha: .1) {
box-shadow: inset $x-axis $y-axis $blur rgba(0, 0, 0, $alpha);
}

Loading…
Cancel
Save