Browse Source

Save queries.

pull/313/head
Sebastian Stehle 8 years ago
parent
commit
a41090e997
  1. 20
      src/Squidex.Domain.Apps.Entities/Apps/AppUISettingsGrain.cs
  2. 2
      src/Squidex.Domain.Apps.Entities/Apps/IAppUISettingsGrain.cs
  3. 19
      src/Squidex/Areas/Api/Controllers/UI/Models/UpdateSettingDto.cs
  4. 6
      src/Squidex/Areas/Api/Controllers/UI/UIController.cs
  5. 8
      src/Squidex/app/features/assets/pages/assets-page.component.html
  6. 21
      src/Squidex/app/features/assets/pages/assets-page.component.scss
  7. 34
      src/Squidex/app/features/content/pages/contents/contents-page.component.html
  8. 37
      src/Squidex/app/features/content/pages/contents/contents-page.component.ts
  9. 37
      src/Squidex/app/features/content/pages/contents/search-form.component.html
  10. 10
      src/Squidex/app/features/content/pages/contents/search-form.component.scss
  11. 48
      src/Squidex/app/features/content/pages/contents/search-form.component.ts
  12. 1
      src/Squidex/app/shared/internal.ts
  13. 2
      src/Squidex/app/shared/services/ui.service.ts
  14. 14
      src/Squidex/app/shared/state/contents.forms.ts
  15. 79
      src/Squidex/app/shared/state/schema-queries.spec.ts
  16. 78
      src/Squidex/app/shared/state/schema-queries.ts
  17. 15
      src/Squidex/app/shared/state/ui.state.ts
  18. 38
      src/Squidex/app/theme/_panels.scss
  19. 8
      src/Squidex/app/theme/icomoon/demo-files/demo.css
  20. 1052
      src/Squidex/app/theme/icomoon/demo.html
  21. BIN
      src/Squidex/app/theme/icomoon/fonts/icomoon.eot
  22. 3
      src/Squidex/app/theme/icomoon/fonts/icomoon.svg
  23. BIN
      src/Squidex/app/theme/icomoon/fonts/icomoon.ttf
  24. BIN
      src/Squidex/app/theme/icomoon/fonts/icomoon.woff
  25. 2266
      src/Squidex/app/theme/icomoon/selection.json
  26. 118
      src/Squidex/app/theme/icomoon/style.css
  27. 4
      tests/Squidex.Domain.Apps.Entities.Tests/Apps/AppUISettingsGrainTests.cs

20
src/Squidex.Domain.Apps.Entities/Apps/AppUISettingsGrain.cs

@ -18,8 +18,14 @@ namespace Squidex.Domain.Apps.Entities.Apps
public sealed class AppUISettingsGrain : GrainOfGuid, IAppUISettingsGrain
{
private readonly IStore<Guid> store;
private IPersistence<JObject> persistence;
private JObject state = new JObject();
private IPersistence<State> persistence;
private State state = new State();
[CollectionName("UISettings")]
public sealed class State
{
public JObject Settings { get; set; } = new JObject();
}
public AppUISettingsGrain(IStore<Guid> store)
{
@ -30,19 +36,19 @@ namespace Squidex.Domain.Apps.Entities.Apps
public override Task OnActivateAsync(Guid key)
{
persistence = store.WithSnapshots<JObject, Guid>(GetType(), key, x => state = x);
persistence = store.WithSnapshots<State, Guid>(GetType(), key, x => state = x);
return persistence.ReadAsync();
}
public Task<J<JObject>> GetAsync()
{
return Task.FromResult(state.AsJ());
return Task.FromResult(state.Settings.AsJ());
}
public Task SetAsync(J<JObject> setting)
public Task SetAsync(J<JObject> settings)
{
state = setting;
state.Settings = settings;
return persistence.WriteSnapshotAsync(state);
}
@ -81,7 +87,7 @@ namespace Squidex.Domain.Apps.Entities.Apps
key = segments[segments.Length - 1];
var current = state;
var current = state.Settings;
if (segments.Length > 1)
{

2
src/Squidex.Domain.Apps.Entities/Apps/IAppUISettingsGrain.cs

@ -18,7 +18,7 @@ namespace Squidex.Domain.Apps.Entities.Apps
Task SetAsync(string path, J<JToken> value);
Task SetAsync(J<JObject> setting);
Task SetAsync(J<JObject> settings);
Task RemoveAsync(string path);
}

19
src/Squidex/Areas/Api/Controllers/UI/Models/UpdateSettingDto.cs

@ -0,0 +1,19 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using Newtonsoft.Json.Linq;
namespace Squidex.Areas.Api.Controllers.UI.Models
{
public sealed class UpdateSettingDto
{
/// <summary>
/// The value for the setting.
/// </summary>
public JToken Value { get; set; }
}
}

6
src/Squidex/Areas/Api/Controllers/UI/UIController.cs

@ -22,7 +22,9 @@ namespace Squidex.Areas.Api.Controllers.UI
/// <summary>
/// Manages ui settings and configs.
/// </summary>
[ApiAuthorize]
[ApiExceptionFilter]
[AppApi]
[SwaggerTag(nameof(UI))]
public sealed class UIController : ApiController
{
@ -71,9 +73,9 @@ namespace Squidex.Areas.Api.Controllers.UI
[HttpPut]
[Route("apps/{app}/ui/settings/{key}")]
[ApiCosts(0)]
public async Task<IActionResult> PutSetting(string app, string key, [FromBody] JToken value)
public async Task<IActionResult> PutSetting(string app, string key, [FromBody] UpdateSettingDto request)
{
await grainFactory.GetGrain<IAppUISettingsGrain>(App.Id).SetAsync(key, value);
await grainFactory.GetGrain<IAppUISettingsGrain>(App.Id).SetAsync(key, request.Value);
return NoContent();
}

8
src/Squidex/app/features/assets/pages/assets-page.component.html

@ -23,18 +23,18 @@
</ng-container>
<ng-container sidebar>
<div class="section">
<a class="row tag" (click)="resetTags()" [class.active]="assetsState.isTagSelectionEmpty()">
<div class="sidebar-section">
<a class="row sidebar-item" (click)="resetTags()" [class.active]="assetsState.isTagSelectionEmpty()">
<div class="col">
All Assets
</div>
</a>
</div>
<div class="section">
<div class="sidebar-section">
<h3>Tags</h3>
<a class="row tag" *ngFor="let tag of assetsState.tags | async" (click)="toggleTag(tag.name)" [class.active]="assetsState.isTagSelected(tag.name)">
<a class="row sidebar-item" *ngFor="let tag of assetsState.tags | async" (click)="toggleTag(tag.name)" [class.active]="assetsState.isTagSelected(tag.name)">
<div class="col">
{{tag.name}}
</div>

21
src/Squidex/app/features/assets/pages/assets-page.component.scss

@ -4,25 +4,4 @@
.section {
border-top: 1px solid $color-border;
padding: 1rem;
}
.tag {
& {
padding: .25rem 0;
}
&.active {
font-weight: bold;
}
&.active,
&:hover {
background: $color-background;
}
}
a {
&.tag {
cursor: pointer !important;
}
}

34
src/Squidex/app/features/content/pages/contents/contents-page.component.html

@ -1,6 +1,6 @@
<sqx-title message="{app} | {schema} | Contents" parameter1="app" parameter2="schema" [value1]="appsState.appName" [value2]="schema.displayName"></sqx-title>
<sqx-panel desiredWidth="*" contentClass="grid">
<sqx-panel desiredWidth="*" contentClass="grid" showSidebar="true" sidebarClass="wide">
<ng-container title>
<ng-container *ngIf="contentsState.isArchive | async; else noArchive">
Archive
@ -23,7 +23,8 @@
<sqx-search-form formClass="form"
(queryChanged)="search($event)"
[query]="contentsState.contentsQuery | async"
[query]="contentsState.contentsQuery | async"
[queries]="schemaQueries"
(archivedChanged)="goArchive($event)"
[archived]="contentsState.isArchive | async"
enableShortcut="true">
@ -118,6 +119,35 @@
<div class="grid-footer">
<sqx-pager [pager]="contentsState.contentsPager | async" (prev)="goPrev()" (next)="goNext()"></sqx-pager>
</div>
</ng-container>
<ng-container sidebar>
<ng-container *ngIf="schemaQueries.queries | async; let queries">
<div class="sidebar-section">
<h3>Queries</h3>
<a class="row sidebar-item" *ngFor="let query of schemaQueries.defaultQueries" (click)="search(query.filter)" [class.active]="isSelectedQuery(query.filter)">
<div class="col truncate">
{{query.name}}
</div>
</a>
</div>
<div class="sidebar-section" *ngIf="queries.length > 0">
<h3>Bookmarks</h3>
<a class="row sidebar-item" *ngFor="let query of queries" (click)="search(query.filter)" [class.active]="isSelectedQuery(query.filter)">
<div class="col truncate">
{{query.name}}
</div>
<div class="col col-auto">
<a class="sidebar-item-remove" (click)="schemaQueries.remove(query.name)">
<i class="icon-close"></i>
</a>
</div>
</a>
</div>
</ng-container>
</ng-container>
</sqx-panel>

37
src/Squidex/app/features/content/pages/contents/contents-page.component.ts

@ -18,7 +18,9 @@ import {
LanguagesState,
ModalModel,
SchemaDetailsDto,
SchemasState
SchemaQueries,
SchemasState,
UIState
} from '@app/shared';
import { DueTimeSelectorComponent } from './../../shared/due-time-selector.component';
@ -34,6 +36,7 @@ export class ContentsPageComponent implements OnDestroy, OnInit {
private selectedSchemaSubscription: Subscription;
public schema: SchemaDetailsDto;
public schemaQueries: SchemaQueries;
public searchModal = new ModalModel();
@ -55,7 +58,8 @@ export class ContentsPageComponent implements OnDestroy, OnInit {
public readonly appsState: AppsState,
public readonly contentsState: ContentsState,
private readonly languagesState: LanguagesState,
private readonly schemasState: SchemasState
private readonly schemasState: SchemasState,
private readonly uiState: UIState
) {
}
@ -72,6 +76,7 @@ export class ContentsPageComponent implements OnDestroy, OnInit {
this.resetSelection();
this.schema = schema!;
this.schemaQueries = new SchemaQueries(this.uiState, this.schema.name);
this.contentsState.init().pipe(onErrorResumeNext()).subscribe();
});
@ -103,38 +108,42 @@ export class ContentsPageComponent implements OnDestroy, OnInit {
}
public publish(content: ContentDto) {
this.changeContentItems([content], 'Publish', false);
this.changeContentItems([content], 'Publish');
}
public publishSelected(scheduled: boolean) {
this.changeContentItems(this.select(c => c.status !== 'Published'), 'Publish', false);
public publishSelected() {
this.changeContentItems(this.select(c => c.status !== 'Published'), 'Publish');
}
public unpublish(content: ContentDto) {
this.changeContentItems([content], 'Unpublish', false);
this.changeContentItems([content], 'Unpublish');
}
public unpublishSelected(scheduled: boolean) {
this.changeContentItems(this.select(c => c.status === 'Published'), 'Unpublish', false);
public unpublishSelected() {
this.changeContentItems(this.select(c => c.status === 'Published'), 'Unpublish');
}
public archive(content: ContentDto) {
this.changeContentItems([content], 'Archive', true);
this.changeContentItems([content], 'Archive');
}
public archiveSelected(scheduled: boolean) {
this.changeContentItems(this.select(), 'Archive', true);
public archiveSelected() {
this.changeContentItems(this.select(), 'Archive');
}
public restore(content: ContentDto) {
this.changeContentItems([content], 'Restore', true);
this.changeContentItems([content], 'Restore');
}
public restoreSelected(scheduled: boolean) {
this.changeContentItems(this.select(), 'Restore', true);
this.changeContentItems(this.select(), 'Restore');
}
private changeContentItems(contents: ContentDto[], action: string, reload: boolean) {
public isSelectedQuery(query: string) {
return query === this.contentsState.snapshot.contentsQuery || (!query && !this.contentsState.contentsQuery);
}
private changeContentItems(contents: ContentDto[], action: string) {
if (contents.length === 0) {
return;
}

37
src/Squidex/app/features/content/pages/contents/search-form.component.html

@ -8,6 +8,18 @@
<a class="expand-search" (click)="searchModal.toggle()" #archive>
<i class="icon-caret-down"></i>
</a>
<ng-container *ngIf="saveKey | async; else notBookmarked; let key">
<a class="save-search" (click)="queries.remove(key)">
<i class="icon-star-full"></i>
</a>
</ng-container>
<ng-template #notBookmarked>
<a class="save-search" (click)="saveQuery()" *ngIf="contentsFilterValue | async">
<i class="icon-star-empty"></i>
</a>
</ng-template>
</form>
<sqx-onboarding-tooltip id="contentArchive" [for]="archive" position="bottomRight" after="60000">
@ -57,4 +69,27 @@
Read more about filtering in the <a href="https://docs.squidex.io/04-guides/02-api.html" target="_blank">Documentation</a>.
</div>
</div>
</div>
</div>
<ng-container *sqxModalView="saveQueryDialog;onRoot:true">
<form [formGroup]="saveQueryForm.form" (ngSubmit)="saveQueryComplete()">
<sqx-modal-dialog (closed)="saveQueryDialog.hide()">
<ng-container title>
Name your query
</ng-container>
<ng-container content>
<div class="form-group">
<sqx-control-errors for="name" submitOnly="true" [submitted]="saveQueryForm.submitted | async"></sqx-control-errors>
<input type="text" class="form-control" id="appName" formControlName="name" autocomplete="off" sqxFocusOnInit />
</div>
</ng-container>
<ng-container footer>
<button type="reset" class="float-left btn btn-secondary" (click)="saveQueryComplete()" [disabled]="saveQueryForm.submitted | async">Cancel</button>
<button type="submit" class="float-right btn btn-success">Create</button>
</ng-container>
</sqx-modal-dialog>
</form>
</ng-container>

10
src/Squidex/app/features/content/pages/contents/search-form.component.scss

@ -10,7 +10,7 @@
}
.form-control-expandable {
padding-right: 1.5rem;
padding-right: 3rem;
}
.form-horizontal {
@ -26,6 +26,14 @@
text-align: right;
}
.save-search {
@include absolute(8px, 24px, auto, auto);
color: $color-border-dark !important;
font-size: .9rem;
font-weight: normal;
cursor: pointer !important;
}
.expand-search {
@include absolute(8px, 8px, auto, auto);
color: $color-border-dark !important;

48
src/Squidex/app/features/content/pages/contents/search-form.component.ts

@ -5,10 +5,12 @@
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/
import { ChangeDetectionStrategy, Component, EventEmitter, Input, OnChanges, Output } from '@angular/core';
import { ChangeDetectionStrategy, Component, EventEmitter, Input, OnChanges, OnInit, Output } from '@angular/core';
import { FormBuilder, FormControl } from '@angular/forms';
import { ModalModel } from '@app/shared';
import { ModalModel, SaveQueryForm, SchemaQueries } from '@app/shared';
import { Observable } from 'rxjs';
import { shareReplay } from 'rxjs/operators';
@Component({
selector: 'sqx-search-form',
@ -16,7 +18,10 @@ import { ModalModel } from '@app/shared';
templateUrl: './search-form.component.html',
changeDetection: ChangeDetectionStrategy.OnPush
})
export class SearchFormComponent implements OnChanges {
export class SearchFormComponent implements OnChanges, OnInit {
@Input()
public queries: SchemaQueries;
@Input()
public query = '';
@ -29,6 +34,9 @@ export class SearchFormComponent implements OnChanges {
@Output()
public archivedChanged = new EventEmitter<boolean>();
@Input()
public schemaName = '';
@Input()
public canArchive = true;
@ -39,6 +47,9 @@ export class SearchFormComponent implements OnChanges {
public formClass = 'form-inline search-form';
public contentsFilter = new FormControl();
public contentsFilterValue = this.contentsFilter.valueChanges.pipe(shareReplay(1));
public saveKey: Observable<string | null>;
public searchModal = new ModalModel();
public searchForm =
@ -48,21 +59,44 @@ export class SearchFormComponent implements OnChanges {
odataSearch: ''
});
public saveQueryDialog = new ModalModel();
public saveQueryForm = new SaveQueryForm(this.formBuilder);
constructor(
private readonly formBuilder: FormBuilder
) {
}
public search() {
this.invalidate(this.contentsFilter.value);
this.queryChanged.emit(this.contentsFilter.value);
public ngOnInit() {
this.saveKey = this.queries.getSaveKey(this.contentsFilter.valueChanges);
}
public ngOnChanges() {
this.invalidate(this.query);
}
public saveQuery() {
this.saveQueryDialog.show();
}
public saveQueryComplete() {
const value = this.saveQueryForm.submit();
if (value) {
this.queries.add(value.name, this.contentsFilter.value);
this.saveQueryForm.submitCompleted();
}
this.saveQueryDialog.hide();
}
public search() {
this.invalidate(this.contentsFilter.value);
this.queryChanged.emit(this.contentsFilter.value);
}
private invalidate(query: string) {
if (query === this.contentsFilter.value) {
return;

1
src/Squidex/app/shared/internal.ts

@ -61,6 +61,7 @@ export * from './state/rule-events.state';
export * from './state/rules.state';
export * from './state/schemas.forms';
export * from './state/schemas.state';
export * from './state/schema-queries';
export * from './state/ui.state';
export * from './utils/messages';

2
src/Squidex/app/shared/services/ui.service.ts

@ -37,7 +37,7 @@ export class UIService {
public putSetting(appName: string, key: string, value: any): Observable<any> {
const url = this.apiUrl.buildUrl(`api/apps/${appName}/ui/settings/${key}`);
return this.http.put(url, value);
return this.http.put(url, { value });
}
public deleteSetting(appName: string, key: string): Observable<any> {

14
src/Squidex/app/shared/state/contents.forms.ts

@ -8,7 +8,7 @@
// tslint:disable:prefer-for-of
import { FormArray, FormControl, FormGroup, ValidatorFn, Validators } from '@angular/forms';
import { FormArray, FormBuilder, FormControl, FormGroup, ValidatorFn, Validators } from '@angular/forms';
import {
DateTime,
@ -36,6 +36,18 @@ import {
TagsFieldPropertiesDto
} from './../services/schemas.types';
export class SaveQueryForm extends Form<FormGroup> {
constructor(formBuilder: FormBuilder) {
super(formBuilder.group({
name: ['',
[
Validators.required
]
]
}));
}
}
export class FieldFormatter implements FieldPropertiesVisitor<string> {
constructor(
private readonly value: any

79
src/Squidex/app/shared/state/schema-queries.spec.ts

@ -0,0 +1,79 @@
/*
* Squidex Headless CMS
*
* @license
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/
import { BehaviorSubject } from 'rxjs';
import { IMock, Mock, Times } from 'typemoq';
import { Query, SchemaQueries } from './schema-queries';
import { UIState } from './ui.state';
describe('SchemaQueries', () => {
const schema = 'my-schema';
let uiState: IMock<UIState>;
let filter = new BehaviorSubject('');
let queries = new BehaviorSubject({});
let schemaQueries: SchemaQueries;
beforeEach(() => {
uiState = Mock.ofType<UIState>();
uiState.setup(x => x.get('schemas.my-schema.queries', {}))
.returns(() => queries);
queries.next({
key1: 'query1',
key2: 'query2'
});
schemaQueries = new SchemaQueries(uiState.object, schema);
});
it('should load queries', () => {
let converted: Query[];
schemaQueries.queries.subscribe(x => {
converted = x;
});
expect(converted!).toEqual([
{
name: 'key1',
nameSortable: 'KEY1',
filter: 'query1'
}, {
name: 'key2',
nameSortable: 'KEY2',
filter: 'query2'
}
]);
});
it('should provide key', () => {
let key: string;
schemaQueries.getSaveKey(filter).subscribe(x => {
key = x!;
});
filter.next('query2');
expect(key!).toEqual('key2');
});
it('should forward add call to state', () => {
schemaQueries.add('key3', 'filter3');
uiState.verify(x => x.set('schemas.my-schema.queries.key3', 'filter3'), Times.once());
});
it('should forward remove call to state', () => {
schemaQueries.remove('key3');
uiState.verify(x => x.remove('schemas.my-schema.queries.key3'), Times.once());
});
});

78
src/Squidex/app/shared/state/schema-queries.ts

@ -0,0 +1,78 @@
/*
* Squidex Headless CMS
*
* @license
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/
import { combineLatest, Observable } from 'rxjs';
import { map } from 'rxjs/operators';
import { UIState } from './ui.state';
export interface Query {
name: string;
nameSortable?: string;
filter: string;
}
export class SchemaQueries {
public queries: Observable<Query[]>;
public defaultQueries: Query[] = [{
name: 'All (newest first)', filter: ''
}, {
name: 'All (oldest first)', filter: '$orderby=lastModified desc'
}];
constructor(
private readonly uiState: UIState,
private readonly schemaName: string
) {
this.queries = this.uiState.get(`schemas.${this.schemaName}.queries`, {}).pipe(
map(x => {
let queries: Query[] = Object.keys(x).map(y => ({ name: y, filter: x[y] }));
for (let query of queries) {
query.nameSortable = query.name.toUpperCase();
}
queries = queries.sort((a, b) => {
if (a.nameSortable! < b.nameSortable!) {
return -1;
}
if (a.nameSortable! > b.nameSortable!) {
return 1;
}
return 0;
});
return queries;
})
);
}
public add(key: string, filter: string) {
this.uiState.set(`schemas.${this.schemaName}.queries.${key}`, filter);
}
public remove(key: string) {
this.uiState.remove(`schemas.${this.schemaName}.queries.${key}`);
}
public getSaveKey(filter$: Observable<string>): Observable<string | null> {
return combineLatest(this.queries, filter$).pipe(
map(project => {
const filter = project[1];
if (filter) {
for (let query of project[0]) {
if (query.filter === filter) {
return query.name;
}
}
}
return null;
}));
}
}

15
src/Squidex/app/shared/state/ui.state.ts

@ -6,8 +6,7 @@
*/
import { Injectable } from '@angular/core';
import { Observable } from 'rxjs';
import { distinctUntilChanged, map, tap } from 'rxjs/operators';
import { distinctUntilChanged, map } from 'rxjs/operators';
import { State, Types } from '@app/framework';
@ -47,19 +46,15 @@ export class UIState extends State<Snapshot> {
}
}
public load(reset = false): Observable<any> {
public load(reset = false) {
if (!reset) {
this.resetState();
}
return this.loadInternal();
}
private loadInternal(): Observable<any> {
return this.uiService.getSettings(this.appName).pipe(
tap(dtos => {
this.uiService.getSettings(this.appName)
.subscribe(dtos => {
return this.next({ settings: dtos });
}));
});
}
public set(path: string, value: any) {

38
src/Squidex/app/theme/_panels.scss

@ -280,6 +280,44 @@
}
}
}
&-section {
border-top: 1px solid $color-border;
padding: 1rem;
}
&-item {
& {
padding: .25rem 0;
font-size: .9rem;
font-weight: normal;
}
&-remove {
visibility: hidden;
}
&.active {
font-weight: bold;
}
&.active,
&:hover {
background: $color-background;
}
&:hover {
.sidebar-item-remove {
visibility: visible;
}
}
}
}
a {
&.sidebar-item {
cursor: pointer !important;
}
}
//

8
src/Squidex/app/theme/icomoon/demo-files/demo.css

@ -147,16 +147,16 @@ p {
font-size: 16px;
}
.fs1 {
font-size: 24px;
font-size: 32px;
}
.fs2 {
font-size: 32px;
font-size: 24px;
}
.fs3 {
font-size: 28px;
font-size: 32px;
}
.fs4 {
font-size: 32px;
font-size: 28px;
}
.fs5 {
font-size: 32px;

1052
src/Squidex/app/theme/icomoon/demo.html

File diff suppressed because it is too large

BIN
src/Squidex/app/theme/icomoon/fonts/icomoon.eot

Binary file not shown.

3
src/Squidex/app/theme/icomoon/fonts/icomoon.svg

@ -99,6 +99,9 @@
<glyph unicode="&#xe959;" glyph-name="action-Medium" d="M121.429 688.64c1.28 12.587-3.541 25.003-12.928 33.451l-95.573 115.2v17.195h296.832l229.461-503.253 201.728 503.253h283.051v-17.195l-81.792-78.379c-7.040-5.376-10.539-14.208-9.088-22.955v-576c-1.451-8.704 2.048-17.493 9.088-22.912l79.787-78.379v-17.195h-401.493v17.195l82.645 80.299c8.107 8.107 8.107 10.496 8.107 22.955v465.536l-229.973-584.021h-31.019l-267.733 584.021v-391.424c-2.219-16.469 3.243-33.024 14.805-44.928l107.52-130.56v-17.152h-304.853v17.195l107.52 130.56c11.52 11.861 16.64 28.587 13.909 44.885v452.608z" />
<glyph unicode="&#xe95a;" glyph-name="support" d="M726 426.667c0-24-20-42-44-42h-426l-170-172v598c0 24 18 42 42 42h554c24 0 44-18 44-42v-384zM896 682.667c24 0 42-18 42-42v-640l-170 170h-470c-24 0-42 18-42 42v86h554v384h86z" />
<glyph unicode="&#xe95b;" glyph-name="backup" d="M512 810.667c212 0 384-172 384-384s-172-384-384-384c-88 0-170 30-234 80l60 60c50-34 110-54 174-54 166 0 298 132 298 298s-132 298-298 298-298-132-298-298h128l-172-170-170 170h128c0 212 172 384 384 384zM598 426.667c0-46-40-86-86-86s-86 40-86 86 40 86 86 86 86-40 86-86z" />
<glyph unicode="&#xe95c;" glyph-name="twitter, action-Tweet" d="M1024 733.6c-37.6-16.8-78.2-28-120.6-33 43.4 26 76.6 67.2 92.4 116.2-40.6-24-85.6-41.6-133.4-51-38.4 40.8-93 66.2-153.4 66.2-116 0-210-94-210-210 0-16.4 1.8-32.4 5.4-47.8-174.6 8.8-329.4 92.4-433 219.6-18-31-28.4-67.2-28.4-105.6 0-72.8 37-137.2 93.4-174.8-34.4 1-66.8 10.6-95.2 26.2 0-0.8 0-1.8 0-2.6 0-101.8 72.4-186.8 168.6-206-17.6-4.8-36.2-7.4-55.4-7.4-13.6 0-26.6 1.4-39.6 3.8 26.8-83.4 104.4-144.2 196.2-146-72-56.4-162.4-90-261-90-17 0-33.6 1-50.2 3 93.2-59.8 203.6-94.4 322.2-94.4 386.4 0 597.8 320.2 597.8 597.8 0 9.2-0.2 18.2-0.6 27.2 41 29.4 76.6 66.4 104.8 108.6z" />
<glyph unicode="&#xe95d;" glyph-name="star-full" d="M1024 562.95l-353.78 51.408-158.22 320.582-158.216-320.582-353.784-51.408 256-249.538-60.432-352.352 316.432 166.358 316.432-166.358-60.434 352.352 256.002 249.538z" />
<glyph unicode="&#xe95e;" glyph-name="star-empty" d="M1024 562.95l-353.78 51.408-158.22 320.582-158.216-320.582-353.784-51.408 256-249.538-60.432-352.352 316.432 166.358 316.432-166.358-60.434 352.352 256.002 249.538zM512 206.502l-223.462-117.48 42.676 248.83-180.786 176.222 249.84 36.304 111.732 226.396 111.736-226.396 249.836-36.304-180.788-176.222 42.678-248.83-223.462 117.48z" />
<glyph unicode="&#xe9ca;" glyph-name="earth" d="M512 960c-282.77 0-512-229.23-512-512s229.23-512 512-512 512 229.23 512 512-229.23 512-512 512zM512-0.002c-62.958 0-122.872 13.012-177.23 36.452l233.148 262.29c5.206 5.858 8.082 13.422 8.082 21.26v96c0 17.674-14.326 32-32 32-112.99 0-232.204 117.462-233.374 118.626-6 6.002-14.14 9.374-22.626 9.374h-128c-17.672 0-32-14.328-32-32v-192c0-12.122 6.848-23.202 17.69-28.622l110.31-55.156v-187.886c-116.052 80.956-192 215.432-192 367.664 0 68.714 15.49 133.806 43.138 192h116.862c8.488 0 16.626 3.372 22.628 9.372l128 128c6 6.002 9.372 14.14 9.372 22.628v77.412c40.562 12.074 83.518 18.588 128 18.588 70.406 0 137.004-16.26 196.282-45.2-4.144-3.502-8.176-7.164-12.046-11.036-36.266-36.264-56.236-84.478-56.236-135.764s19.97-99.5 56.236-135.764c36.434-36.432 85.218-56.264 135.634-56.26 3.166 0 6.342 0.080 9.518 0.236 13.814-51.802 38.752-186.656-8.404-372.334-0.444-1.744-0.696-3.488-0.842-5.224-81.324-83.080-194.7-134.656-320.142-134.656z" />
<glyph unicode="&#xf00a;" glyph-name="grid" d="M292.571 237.714v-109.714c0-30.286-24.571-54.857-54.857-54.857h-182.857c-30.286 0-54.857 24.571-54.857 54.857v109.714c0 30.286 24.571 54.857 54.857 54.857h182.857c30.286 0 54.857-24.571 54.857-54.857zM292.571 530.286v-109.714c0-30.286-24.571-54.857-54.857-54.857h-182.857c-30.286 0-54.857 24.571-54.857 54.857v109.714c0 30.286 24.571 54.857 54.857 54.857h182.857c30.286 0 54.857-24.571 54.857-54.857zM658.286 237.714v-109.714c0-30.286-24.571-54.857-54.857-54.857h-182.857c-30.286 0-54.857 24.571-54.857 54.857v109.714c0 30.286 24.571 54.857 54.857 54.857h182.857c30.286 0 54.857-24.571 54.857-54.857zM292.571 822.857v-109.714c0-30.286-24.571-54.857-54.857-54.857h-182.857c-30.286 0-54.857 24.571-54.857 54.857v109.714c0 30.286 24.571 54.857 54.857 54.857h182.857c30.286 0 54.857-24.571 54.857-54.857zM658.286 530.286v-109.714c0-30.286-24.571-54.857-54.857-54.857h-182.857c-30.286 0-54.857 24.571-54.857 54.857v109.714c0 30.286 24.571 54.857 54.857 54.857h182.857c30.286 0 54.857-24.571 54.857-54.857zM1024 237.714v-109.714c0-30.286-24.571-54.857-54.857-54.857h-182.857c-30.286 0-54.857 24.571-54.857 54.857v109.714c0 30.286 24.571 54.857 54.857 54.857h182.857c30.286 0 54.857-24.571 54.857-54.857zM658.286 822.857v-109.714c0-30.286-24.571-54.857-54.857-54.857h-182.857c-30.286 0-54.857 24.571-54.857 54.857v109.714c0 30.286 24.571 54.857 54.857 54.857h182.857c30.286 0 54.857-24.571 54.857-54.857zM1024 530.286v-109.714c0-30.286-24.571-54.857-54.857-54.857h-182.857c-30.286 0-54.857 24.571-54.857 54.857v109.714c0 30.286 24.571 54.857 54.857 54.857h182.857c30.286 0 54.857-24.571 54.857-54.857zM1024 822.857v-109.714c0-30.286-24.571-54.857-54.857-54.857h-182.857c-30.286 0-54.857 24.571-54.857 54.857v109.714c0 30.286 24.571 54.857 54.857 54.857h182.857c30.286 0 54.857-24.571 54.857-54.857z" />
<glyph unicode="&#xf0c9;" glyph-name="list" horiz-adv-x="878" d="M877.714 182.857v-73.143c0-20-16.571-36.571-36.571-36.571h-804.571c-20 0-36.571 16.571-36.571 36.571v73.143c0 20 16.571 36.571 36.571 36.571h804.571c20 0 36.571-16.571 36.571-36.571zM877.714 475.428v-73.143c0-20-16.571-36.571-36.571-36.571h-804.571c-20 0-36.571 16.571-36.571 36.571v73.143c0 20 16.571 36.571 36.571 36.571h804.571c20 0 36.571-16.571 36.571-36.571zM877.714 768v-73.143c0-20-16.571-36.571-36.571-36.571h-804.571c-20 0-36.571 16.571-36.571 36.571v73.143c0 20 16.571 36.571 36.571 36.571h804.571c20 0 36.571-16.571 36.571-36.571z" />

Before

Width:  |  Height:  |  Size: 79 KiB

After

Width:  |  Height:  |  Size: 80 KiB

BIN
src/Squidex/app/theme/icomoon/fonts/icomoon.ttf

Binary file not shown.

BIN
src/Squidex/app/theme/icomoon/fonts/icomoon.woff

Binary file not shown.

2266
src/Squidex/app/theme/icomoon/selection.json

File diff suppressed because it is too large

118
src/Squidex/app/theme/icomoon/style.css

@ -1,10 +1,10 @@
@font-face {
font-family: 'icomoon';
src: url('fonts/icomoon.eot?za0y3d');
src: url('fonts/icomoon.eot?za0y3d#iefix') format('embedded-opentype'),
url('fonts/icomoon.ttf?za0y3d') format('truetype'),
url('fonts/icomoon.woff?za0y3d') format('woff'),
url('fonts/icomoon.svg?za0y3d#icomoon') format('svg');
src: url('fonts/icomoon.eot?jk799');
src: url('fonts/icomoon.eot?jk799#iefix') format('embedded-opentype'),
url('fonts/icomoon.ttf?jk799') format('truetype'),
url('fonts/icomoon.woff?jk799') format('woff'),
url('fonts/icomoon.svg?jk799#icomoon') format('svg');
font-weight: normal;
font-style: normal;
}
@ -24,6 +24,66 @@
-moz-osx-font-smoothing: grayscale;
}
.icon-star-full:before {
content: "\e95d";
}
.icon-star-empty:before {
content: "\e95e";
}
.icon-twitter:before {
content: "\e95c";
}
.icon-action-Tweet:before {
content: "\e95c";
}
.icon-hour-glass:before {
content: "\e954";
}
.icon-spinner:before {
content: "\e953";
}
.icon-clock:before {
content: "\e950";
}
.icon-bin2:before {
content: "\e902";
}
.icon-earth:before {
content: "\e9ca";
}
.icon-elapsed:before {
content: "\e943";
}
.icon-google:before {
content: "\e93b";
}
.icon-lock:before {
content: "\e934";
}
.icon-microsoft:before {
content: "\e940";
}
.icon-action-AzureQueue:before {
content: "\e940";
}
.icon-pause:before {
content: "\e92f";
}
.icon-play:before {
content: "\e930";
}
.icon-reset:before {
content: "\e92e";
}
.icon-settings2:before {
content: "\e92d";
}
.icon-timeout:before {
content: "\e944";
}
.icon-unlocked:before {
content: "\e933";
}
.icon-backup:before {
content: "\e95b";
}
@ -288,54 +348,6 @@
.icon-action-Webhook:before {
content: "\e947";
}
.icon-hour-glass:before {
content: "\e954";
}
.icon-spinner:before {
content: "\e953";
}
.icon-clock:before {
content: "\e950";
}
.icon-bin2:before {
content: "\e902";
}
.icon-earth:before {
content: "\e9ca";
}
.icon-elapsed:before {
content: "\e943";
}
.icon-google:before {
content: "\e93b";
}
.icon-lock:before {
content: "\e934";
}
.icon-microsoft:before {
content: "\e940";
}
.icon-action-AzureQueue:before {
content: "\e940";
}
.icon-pause:before {
content: "\e92f";
}
.icon-play:before {
content: "\e930";
}
.icon-reset:before {
content: "\e92e";
}
.icon-settings2:before {
content: "\e92d";
}
.icon-timeout:before {
content: "\e944";
}
.icon-unlocked:before {
content: "\e933";
}
.icon-browser:before {
content: "\e935";
}

4
tests/Squidex.Domain.Apps.Entities.Tests/Apps/AppUISettingsGrainTests.cs

@ -18,12 +18,12 @@ namespace Squidex.Domain.Apps.Entities.Apps
public sealed class AppUISettingsGrainTests
{
private readonly IStore<Guid> store = A.Fake<IStore<Guid>>();
private readonly IPersistence<JObject> persistence = A.Fake<IPersistence<JObject>>();
private readonly IPersistence<AppUISettingsGrain.State> persistence = A.Fake<IPersistence<AppUISettingsGrain.State>>();
private readonly AppUISettingsGrain sut;
public AppUISettingsGrainTests()
{
A.CallTo(() => store.WithSnapshots(A<Type>.Ignored, A<Guid>.Ignored, A<Func<JObject, Task>>.Ignored))
A.CallTo(() => store.WithSnapshots(A<Type>.Ignored, A<Guid>.Ignored, A<Func<AppUISettingsGrain.State, Task>>.Ignored))
.Returns(persistence);
sut = new AppUISettingsGrain(store);

Loading…
Cancel
Save