Browse Source

First working version.

pull/65/head
Sebastian Stehle 9 years ago
parent
commit
1aa901bfe0
  1. 2
      Squidex.sln
  2. 2
      src/Squidex.Domain.Apps.Core/Schemas/Field_Generic.cs
  3. 2
      src/Squidex.Domain.Apps.Read.MongoDb/Assets/MongoAssetRepository.cs
  4. 8
      src/Squidex.Domain.Apps.Read.MongoDb/Contents/MongoContentRepository.cs
  5. 104
      src/Squidex.Domain.Apps.Read/Contents/GraphQL/CachingGraphQLInvoker.cs
  6. 187
      src/Squidex.Domain.Apps.Read/Contents/GraphQL/GraphQLModel.cs
  7. 21
      src/Squidex.Domain.Apps.Read/Contents/GraphQL/GraphQLQuery.cs
  8. 10
      src/Squidex.Domain.Apps.Read/Contents/GraphQL/IGraphQLContext.cs
  9. 18
      src/Squidex.Domain.Apps.Read/Contents/GraphQL/IGraphQLInvoker.cs
  10. 183
      src/Squidex.Domain.Apps.Read/Contents/GraphQL/QueryContext.cs
  11. 62
      src/Squidex.Domain.Apps.Read/Contents/GraphQL/Types/AssetGraphType.cs
  12. 69
      src/Squidex.Domain.Apps.Read/Contents/GraphQL/Types/ContentDataGraphType.cs
  13. 23
      src/Squidex.Domain.Apps.Read/Contents/GraphQL/Types/ContentGraphType.cs
  14. 192
      src/Squidex.Domain.Apps.Read/Contents/GraphQL/Types/ContentQueryType.cs
  15. 6
      src/Squidex.Domain.Apps.Read/Contents/Repositories/IContentRepository.cs
  16. 37
      src/Squidex.Domain.Apps.Read/GraphQl/ContentDataGraphType.cs
  17. 36
      src/Squidex.Domain.Apps.Read/GraphQl/ContentFieldGraphType.cs
  18. 94
      src/Squidex.Domain.Apps.Read/GraphQl/GraphQLContext.cs
  19. 6
      src/Squidex/Config/Domain/ReadModule.cs
  20. 2
      src/Squidex/Config/Domain/StoreMongoDbModule.cs
  21. 37
      src/Squidex/Controllers/ContentApi/ContentsController.cs
  22. 4
      src/Squidex/app/app.routes.ts
  23. 33
      src/Squidex/app/features/api/api-area.component.html
  24. 12
      src/Squidex/app/features/api/api-area.component.scss
  25. 26
      src/Squidex/app/features/api/api-area.component.ts
  26. 10
      src/Squidex/app/features/api/declarations.ts
  27. 9
      src/Squidex/app/features/api/index.ts
  28. 50
      src/Squidex/app/features/api/module.ts
  29. 5
      src/Squidex/app/features/api/pages/graphql/graphql-page.component.html
  30. 12
      src/Squidex/app/features/api/pages/graphql/graphql-page.component.scss
  31. 55
      src/Squidex/app/features/api/pages/graphql/graphql-page.component.ts
  32. 72
      src/Squidex/app/framework/angular/panel-container.directive.ts
  33. 27
      src/Squidex/app/framework/angular/panel.component.ts
  34. 1
      src/Squidex/app/framework/declarations.ts
  35. 2
      src/Squidex/app/framework/module.ts
  36. 84
      src/Squidex/app/framework/services/panel.service.spec.ts
  37. 51
      src/Squidex/app/framework/services/panel.service.ts
  38. 5
      src/Squidex/app/shell/pages/app/left-menu.component.html
  39. 44
      src/Squidex/app/theme/icomoon/demo.html
  40. BIN
      src/Squidex/app/theme/icomoon/fonts/icomoon.eot
  41. 4
      src/Squidex/app/theme/icomoon/fonts/icomoon.svg
  42. BIN
      src/Squidex/app/theme/icomoon/fonts/icomoon.ttf
  43. BIN
      src/Squidex/app/theme/icomoon/fonts/icomoon.woff
  44. 237
      src/Squidex/app/theme/icomoon/selection.json
  45. 20
      src/Squidex/app/theme/icomoon/style.css
  46. 6
      src/Squidex/app/theme/theme.scss
  47. 7
      src/Squidex/package.json

2
Squidex.sln

@ -54,7 +54,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Squidex.Domain.Users", "src
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Squidex.Domain.Users.MongoDb", "src\Squidex.Domain.Users.MongoDb\Squidex.Domain.Users.MongoDb.csproj", "{27CF800D-890F-4882-BF05-44EC3233537D}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Squidex.Domain.Users.Tests", "tests\Squidex.Domain.Users.Tests\Squidex.Domain.Users.Tests.csproj", "{42184546-E3CB-4D4F-9495-43979B9C63B9}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Squidex.Domain.Users.Tests", "tests\Squidex.Domain.Users.Tests\Squidex.Domain.Users.Tests.csproj", "{42184546-E3CB-4D4F-9495-43979B9C63B9}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution

2
src/Squidex.Domain.Apps.Core/Schemas/Field_Generic.cs

@ -15,7 +15,7 @@ namespace Squidex.Domain.Apps.Core.Schemas
{
private T properties;
protected T Properties
public T Properties
{
get { return properties; }
}

2
src/Squidex.Domain.Apps.Read.MongoDb/Assets/MongoAssetRepository.cs

@ -44,7 +44,7 @@ namespace Squidex.Domain.Apps.Read.MongoDb.Assets
await Collection.Find(x => assetIds.Contains(x.Id) && x.AppId == appId).Project<BsonDocument>(Project.Include(x => x.Id))
.ToListAsync();
return assetIds.Except(assetEntities.Select(x => x["Id"].AsGuid)).ToList();
return assetIds.Except(assetEntities.Select(x => Guid.Parse(x["_id"].AsString))).ToList();
}
public async Task<IReadOnlyList<IAssetEntity>> QueryAsync(Guid appId, HashSet<string> mimeTypes = null, HashSet<Guid> ids = null, string query = null, int take = 10, int skip = 0)

8
src/Squidex.Domain.Apps.Read.MongoDb/Contents/MongoContentRepository.cs

@ -75,7 +75,7 @@ namespace Squidex.Domain.Apps.Read.MongoDb.Contents
this.modelBuilder = modelBuilder;
}
public async Task<IReadOnlyList<IContentEntity>> QueryAsync(Guid schemaId, bool nonPublished, HashSet<Guid> ids, string odataQuery, IAppEntity appEntity)
public async Task<IReadOnlyList<IContentEntity>> QueryAsync(IAppEntity appEntity, Guid schemaId, bool nonPublished, HashSet<Guid> ids, string odataQuery)
{
var contentEntities = (List<IContentEntity>)null;
@ -121,7 +121,7 @@ namespace Squidex.Domain.Apps.Read.MongoDb.Contents
return contentEntities;
}
public async Task<long> CountAsync(Guid schemaId, bool nonPublished, HashSet<Guid> ids, string odataQuery, IAppEntity appEntity)
public async Task<long> CountAsync(IAppEntity appEntity, Guid schemaId, bool nonPublished, HashSet<Guid> ids, string odataQuery)
{
var contentsCount = 0L;
@ -166,10 +166,10 @@ namespace Squidex.Domain.Apps.Read.MongoDb.Contents
.ToListAsync();
});
return contentIds.Except(contentEntities.Select(x => x["Id"].AsGuid)).ToList();
return contentIds.Except(contentEntities.Select(x => Guid.Parse(x["_id"].AsString))).ToList();
}
public async Task<IContentEntity> FindContentAsync(Guid schemaId, Guid id, IAppEntity appEntity)
public async Task<IContentEntity> FindContentAsync(IAppEntity appEntity, Guid schemaId, Guid id)
{
var contentEntity = (MongoContentEntity)null;

104
src/Squidex.Domain.Apps.Read/Contents/GraphQL/CachingGraphQLInvoker.cs

@ -0,0 +1,104 @@
// ==========================================================================
// CachedGraphQLInvoker.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
using System.Linq;
using System.Threading.Tasks;
using Squidex.Domain.Apps.Read.Apps;
using Squidex.Domain.Apps.Read.Assets.Repositories;
using Squidex.Domain.Apps.Read.Contents.Repositories;
using Squidex.Domain.Apps.Read.Schemas.Repositories;
using Squidex.Infrastructure;
using Squidex.Domain.Apps.Read.Utils;
using Microsoft.Extensions.Caching.Memory;
using Squidex.Infrastructure.CQRS.Events;
using System;
using Squidex.Infrastructure.Tasks;
using Squidex.Domain.Apps.Events;
// ReSharper disable InvertIf
namespace Squidex.Domain.Apps.Read.Contents.GraphQL
{
public sealed class CachingGraphQLInvoker : CachingProviderBase, IGraphQLInvoker, IEventConsumer
{
private readonly IContentRepository contentRepository;
private readonly IAssetRepository assetRepository;
private readonly ISchemaRepository schemaRepository;
public string Name
{
get { return GetType().Name; }
}
public string EventsFilter
{
get { return "^(schema-)|(apps-)"; }
}
public CachingGraphQLInvoker(IMemoryCache cache, ISchemaRepository schemaRepository, IAssetRepository assetRepository, IContentRepository contentRepository)
: base(cache)
{
Guard.NotNull(schemaRepository, nameof(schemaRepository));
Guard.NotNull(assetRepository, nameof(assetRepository));
Guard.NotNull(contentRepository, nameof(contentRepository));
this.schemaRepository = schemaRepository;
this.assetRepository = assetRepository;
this.contentRepository = contentRepository;
}
public Task ClearAsync()
{
return TaskHelper.Done;
}
public Task On(Envelope<IEvent> @event)
{
if (@event.Payload is AppEvent appEvent)
{
Cache.Remove(CreateCacheKey(appEvent.AppId.Id));
}
return TaskHelper.Done;
}
public async Task<object> QueryAsync(IAppEntity appEntity, GraphQLQuery query)
{
Guard.NotNull(appEntity, nameof(appEntity));
Guard.NotNull(query, nameof(query));
var modelContext = await GetModelAsync(appEntity);
var queryContext = new QueryContext(appEntity, contentRepository, assetRepository);
return await modelContext.ExecuteAsync(queryContext, query);
}
private async Task<GraphQLModel> GetModelAsync(IAppEntity appEntity)
{
var cacheKey = CreateCacheKey(appEntity.Id);
var modelContext = Cache.Get<GraphQLModel>(cacheKey);
if (modelContext == null)
{
var schemas = await schemaRepository.QueryAllAsync(appEntity.Id);
modelContext = new GraphQLModel(appEntity, schemas.Where(x => x.IsPublished));
Cache.Set(cacheKey, modelContext);
}
return modelContext;
}
private static object CreateCacheKey(Guid appId)
{
return $"GraphQLModel_{appId}";
}
}
}

187
src/Squidex.Domain.Apps.Read/Contents/GraphQL/GraphQLModel.cs

@ -0,0 +1,187 @@
// ==========================================================================
// GraphQLContext.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using GraphQL;
using GraphQL.Resolvers;
using GraphQL.Types;
using Squidex.Domain.Apps.Core;
using Squidex.Domain.Apps.Core.Contents;
using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Domain.Apps.Read.Apps;
using Squidex.Domain.Apps.Read.Contents.GraphQL.Types;
using Squidex.Domain.Apps.Read.Schemas;
using Squidex.Infrastructure;
using GraphQLSchema = GraphQL.Types.Schema;
// ReSharper disable InvertIf
// ReSharper disable ParameterHidesMember
namespace Squidex.Domain.Apps.Read.Contents.GraphQL
{
public sealed class GraphQLModel : IGraphQLContext
{
private readonly Dictionary<Type, Func<Field, (IGraphType ResolveType, IFieldResolver Resolver)>> fieldInfos;
private readonly Dictionary<Guid, IGraphType> schemaTypes = new Dictionary<Guid, IGraphType>();
private readonly Dictionary<Guid, ISchemaEntity> schemas;
private readonly PartitionResolver partitionResolver;
private readonly IGraphType assetType = new AssetGraphType();
private readonly GraphQLSchema graphQLSchema;
public GraphQLModel(IAppEntity appEntity, IEnumerable<ISchemaEntity> schemas)
{
partitionResolver = appEntity.PartitionResolver;
var defaultResolver =
new FuncFieldResolver<ContentFieldData, object>(c => c.Source.GetOrDefault(c.FieldName));
IGraphType assetListType = new ListGraphType(new NonNullGraphType(assetType));
var stringInfos =
(new StringGraphType(), defaultResolver);
var booleanInfos =
(new BooleanGraphType(), defaultResolver);
var numberInfos =
(new FloatGraphType(), defaultResolver);
var dateTimeInfos =
(new DateGraphType(), defaultResolver);
var jsonInfos =
(new ObjectGraphType(), defaultResolver);
var geolocationInfos =
(new ObjectGraphType(), defaultResolver);
fieldInfos = new Dictionary<Type, Func<Field, (IGraphType ResolveType, IFieldResolver Resolver)>>
{
{
typeof(StringField),
field => stringInfos
},
{
typeof(BooleanField),
field => booleanInfos
},
{
typeof(NumberField),
field => numberInfos
},
{
typeof(DateTimeField),
field => dateTimeInfos
},
{
typeof(JsonField),
field => jsonInfos
},
{
typeof(GeolocationField),
field => geolocationInfos
},
{
typeof(AssetsField),
field =>
{
var resolver = new FuncFieldResolver<ContentFieldData, object>(c =>
{
var context = (QueryContext)c.UserContext;
var contentIds = c.Source.GetOrDefault(c.FieldName);
return context.GetReferencedAssets(contentIds);
});
return (assetListType, resolver);
}
},
{
typeof(ReferencesField),
field =>
{
var schemaId = ((ReferencesField)field).Properties.SchemaId;
var schemaType = GetSchemaType(schemaId);
if (schemaType == null)
{
return (null, null);
}
var resolver = new FuncFieldResolver<ContentFieldData, object>(c =>
{
var context = (QueryContext)c.UserContext;
var contentIds = c.Source.GetOrDefault(c.FieldName);
return context.GetReferencedContents(schemaId, contentIds);
});
var schemaFieldType = new ListGraphType(new NonNullGraphType(GetSchemaType(schemaId)));
return (schemaFieldType, resolver);
}
}
};
this.schemas = schemas.ToDictionary(x => x.Id);
graphQLSchema = new GraphQLSchema { Query = new ContentQueryType(this, this.schemas.Values) };
}
public async Task<object> ExecuteAsync(QueryContext context, GraphQLQuery query)
{
Guard.NotNull(context, nameof(context));
var result = await new DocumentExecuter().ExecuteAsync(options =>
{
options.Query = query.Query;
options.Schema = graphQLSchema;
options.Inputs = query.Variables?.ToInputs() ?? new Inputs();
options.UserContext = context;
options.OperationName = query.OperationName;
}).ConfigureAwait(false);
if (result.Errors != null && result.Errors.Count > 0)
{
var errors = result.Errors.Select(x => new ValidationError(x.Message)).ToArray();
throw new ValidationException("Failed to execute GraphQL query.", errors);
}
return result;
}
public IFieldPartitioning ResolvePartition(Partitioning key)
{
return partitionResolver(key);
}
public IGraphType GetAssetType()
{
return assetType;
}
public (IGraphType ResolveType, IFieldResolver Resolver) GetGraphType(Field field)
{
return fieldInfos[field.GetType()](field);
}
public IGraphType GetSchemaType(Guid schemaId)
{
return schemaTypes.GetOrAdd(schemaId, k =>
{
var schemaEntity = schemas.GetOrDefault(k);
return schemaEntity != null ? new ContentGraphType(schemaEntity.Schema, this) : null;
});
}
}
}

21
src/Squidex.Domain.Apps.Read/Contents/GraphQL/GraphQLQuery.cs

@ -0,0 +1,21 @@
// ==========================================================================
// GraphQLQuery.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
namespace Squidex.Domain.Apps.Read.Contents.GraphQL
{
public class GraphQLQuery
{
public string OperationName { get; set; }
public string NamedQuery { get; set; }
public string Query { get; set; }
public string Variables { get; set; }
}
}

10
src/Squidex.Domain.Apps.Read/GraphQl/IGraphQLContext.cs → src/Squidex.Domain.Apps.Read/Contents/GraphQL/IGraphQLContext.cs

@ -6,20 +6,22 @@
// All rights reserved.
// ==========================================================================
using System;
using GraphQL.Resolvers;
using GraphQL.Types;
using Squidex.Domain.Apps.Core;
using Squidex.Domain.Apps.Core.Schemas;
using Schema = Squidex.Domain.Apps.Core.Schemas.Schema;
namespace Squidex.Domain.Apps.Read.GraphQl
namespace Squidex.Domain.Apps.Read.Contents.GraphQL
{
public interface IGraphQLContext
{
IGraphType GetSchemaListType(Schema schema);
IFieldPartitioning ResolvePartition(Partitioning key);
IGraphType GetAssetType();
IGraphType GetSchemaType(Guid schemaId);
(IGraphType ResolveType, IFieldResolver Resolver) GetGraphType(Field field);
}
}

18
src/Squidex.Domain.Apps.Read/Contents/GraphQL/IGraphQLInvoker.cs

@ -0,0 +1,18 @@
// ==========================================================================
// IGraphQLInvoker.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
using System.Threading.Tasks;
using Squidex.Domain.Apps.Read.Apps;
namespace Squidex.Domain.Apps.Read.Contents.GraphQL
{
public interface IGraphQLInvoker
{
Task<object> QueryAsync(IAppEntity appEntity, GraphQLQuery query);
}
}

183
src/Squidex.Domain.Apps.Read/Contents/GraphQL/QueryContext.cs

@ -0,0 +1,183 @@
// ==========================================================================
// QueryContext.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
using System;
using System.Collections.Generic;
using Newtonsoft.Json.Linq;
using Squidex.Domain.Apps.Read.Contents.Repositories;
using Squidex.Infrastructure;
using System.Collections.Concurrent;
using System.Linq;
using System.Threading.Tasks;
using Squidex.Domain.Apps.Read.Apps;
using Squidex.Domain.Apps.Read.Assets;
using Squidex.Domain.Apps.Read.Assets.Repositories;
// ReSharper disable InvertIf
namespace Squidex.Domain.Apps.Read.Contents.GraphQL
{
public sealed class QueryContext
{
private readonly ConcurrentDictionary<Guid, IContentEntity> cachedContents = new ConcurrentDictionary<Guid, IContentEntity>();
private readonly ConcurrentDictionary<Guid, IAssetEntity> cachedAssets = new ConcurrentDictionary<Guid, IAssetEntity>();
private readonly IContentRepository contentRepository;
private readonly IAssetRepository assetRepository;
private readonly IAppEntity appEntity;
public QueryContext(IAppEntity appEntity, IContentRepository contentRepository, IAssetRepository assetRepository)
{
Guard.NotNull(contentRepository, nameof(contentRepository));
Guard.NotNull(assetRepository, nameof(assetRepository));
Guard.NotNull(appEntity, nameof(appEntity));
this.contentRepository = contentRepository;
this.assetRepository = assetRepository;
this.appEntity = appEntity;
}
public async Task<IAssetEntity> FindAssetAsync(Guid id)
{
var asset = cachedAssets.GetOrDefault(id);
if (asset == null)
{
asset = await assetRepository.FindAssetAsync(id);
if (asset != null)
{
cachedAssets[asset.Id] = asset;
}
}
return asset;
}
public async Task<IContentEntity> FindContentAsync(Guid schemaId, Guid id)
{
var content = cachedContents.GetOrDefault(id);
if (content == null)
{
content = await contentRepository.FindContentAsync(appEntity, schemaId, id);
if (content != null)
{
cachedContents[content.Id] = content;
}
}
return content;
}
public async Task<IReadOnlyList<IAssetEntity>> QueryAssetsAsync(string query, int skip = 0, int take = 10)
{
var assets = await assetRepository.QueryAsync(appEntity.Id, null, null, query, take, skip);
foreach (var asset in assets)
{
cachedAssets[asset.Id] = asset;
}
return assets;
}
public async Task<IReadOnlyList<IContentEntity>> QueryContentsAsync(Guid schemaId, string query)
{
var contents = await contentRepository.QueryAsync(appEntity, schemaId, false, null, query);
foreach (var content in contents)
{
cachedContents[content.Id] = content;
}
return contents;
}
public List<IAssetEntity> GetReferencedAssets(JToken value)
{
var ids = ParseIds(value);
return GetReferencedAssets(ids);
}
public List<IAssetEntity> GetReferencedAssets(ICollection<Guid> ids)
{
Guard.NotNull(ids, nameof(ids));
var notLoadedAssets = new HashSet<Guid>(ids.Where(id => !cachedAssets.ContainsKey(id)));
if (notLoadedAssets.Count > 0)
{
Task.Run(async () =>
{
var assets = await assetRepository.QueryAsync(appEntity.Id, null, notLoadedAssets, string.Empty, int.MaxValue).ConfigureAwait(false);
foreach (var asset in assets)
{
cachedAssets[asset.Id] = asset;
}
}).Wait();
}
return ids.Select(id => cachedAssets.GetOrDefault(id)).Where(x => x != null).ToList();
}
public List<IContentEntity> GetReferencedContents(Guid schemaId, JToken value)
{
var ids = ParseIds(value);
return GetReferencedContents(schemaId, ids);
}
public List<IContentEntity> GetReferencedContents(Guid schemaId, ICollection<Guid> ids)
{
Guard.NotNull(ids, nameof(ids));
var notLoadedContents = new HashSet<Guid>(ids.Where(id => !cachedContents.ContainsKey(id)));
if (notLoadedContents.Count > 0)
{
Task.Run(async () =>
{
var contents = await contentRepository.QueryAsync(appEntity, schemaId, false, notLoadedContents, string.Empty).ConfigureAwait(false);
foreach (var content in contents)
{
cachedContents[content.Id] = content;
}
}).Wait();
}
return ids.Select(id => cachedContents.GetOrDefault(id)).Where(x => x != null).ToList();
}
private static ICollection<Guid> ParseIds(JToken value)
{
try
{
var result = new List<Guid>();
if (value is JArray)
{
foreach (var id in value)
{
result.Add(Guid.Parse(id.ToString()));
}
}
return result;
}
catch
{
return new List<Guid>();
}
}
}
}

62
src/Squidex.Domain.Apps.Read/Contents/GraphQL/Types/AssetGraphType.cs

@ -0,0 +1,62 @@
// ==========================================================================
// AssetGraphType.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
using GraphQL.Types;
using Squidex.Domain.Apps.Read.Assets;
namespace Squidex.Domain.Apps.Read.Contents.GraphQL.Types
{
public sealed class AssetGraphType : ObjectGraphType<IAssetEntity>
{
public AssetGraphType()
{
Name = "AssetDto";
Field("id", x => x.Id.ToString())
.Description("The id of the asset.");
Field("version", x => x.Version)
.Description("The version of the asset.");
Field("created", x => x.Created.ToDateTimeUtc())
.Description("The date and time when the asset has been created.");
Field("createdBy", x => x.CreatedBy.ToString())
.Description("The user that has created the asset.");
Field("lastModified", x => x.LastModified.ToDateTimeUtc())
.Description("The date and time when the asset has been modified last.");
Field("lastModifiedBy", x => x.LastModifiedBy.ToString())
.Description("The user that has updated the asset last.");
Field("mimeType", x => x.MimeType)
.Description("The mime type.");
Field("fileName", x => x.FileName)
.Description("The file name.");
Field("fileSize", x => x.FileSize)
.Description("The size of the file in bytes.");
Field("fileVersion", x => x.FileVersion)
.Description("The version of the file.");
Field("isImage", x => x.IsImage)
.Description("Determines of the created file is an image.");
Field("pixelWidth", x => x.PixelWidth, true)
.Description("The width of the image in pixels if the asset is an image.");
Field("pixelHeight", x => x.PixelHeight, true)
.Description("The height of the image in pixels if the asset is an image.");
Description = "An asset";
}
}
}

69
src/Squidex.Domain.Apps.Read/Contents/GraphQL/Types/ContentDataGraphType.cs

@ -0,0 +1,69 @@
// ==========================================================================
// ContentDataGraphType.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
using GraphQL.Resolvers;
using GraphQL.Types;
using Squidex.Domain.Apps.Core.Contents;
using Squidex.Infrastructure;
using Schema = Squidex.Domain.Apps.Core.Schemas.Schema;
// ReSharper disable InvertIf
namespace Squidex.Domain.Apps.Read.Contents.GraphQL.Types
{
public sealed class ContentDataGraphType : ObjectGraphType<NamedContentData>
{
private static readonly IFieldResolver FieldResolver =
new FuncFieldResolver<NamedContentData, ContentFieldData>(c => c.Source.GetOrDefault(c.FieldName));
public ContentDataGraphType(Schema schema, IGraphQLContext graphQLContext)
{
var schemaName = schema.Properties.Label.WithFallback(schema.Name);
Name = $"{schema.Name.ToPascalCase()}DataDto";
foreach (var field in schema.Fields)
{
var fieldInfo = graphQLContext.GetGraphType(field);
if (fieldInfo.ResolveType != null)
{
var fieldName = field.RawProperties.Label.WithFallback(field.Name);
var fieldGraphType = new ObjectGraphType
{
Name = $"{schema.Name.ToPascalCase()}Data{field.Name.ToPascalCase()}Dto"
};
var partition = graphQLContext.ResolvePartition(field.Paritioning);
foreach (var partitionItem in partition)
{
fieldGraphType.AddField(new FieldType
{
Name = partitionItem.Key,
Resolver = fieldInfo.Resolver,
ResolvedType = fieldInfo.ResolveType,
Description = field.RawProperties.Hints
});
}
fieldGraphType.Description = $"The structure of the {fieldName} of a {schemaName} content type.";
AddField(new FieldType
{
Name = field.Name,
Resolver = FieldResolver,
ResolvedType = fieldGraphType
});
}
}
Description = $"The structure of a {schemaName} content type.";
}
}
}

23
src/Squidex.Domain.Apps.Read/GraphQl/ContentGraphType.cs → src/Squidex.Domain.Apps.Read/Contents/GraphQL/Types/ContentGraphType.cs

@ -9,46 +9,49 @@
using GraphQL.Resolvers;
using GraphQL.Types;
using Squidex.Domain.Apps.Core.Contents;
using Squidex.Domain.Apps.Read.Contents;
using Squidex.Infrastructure;
using Schema = Squidex.Domain.Apps.Core.Schemas.Schema;
namespace Squidex.Domain.Apps.Read.GraphQl
namespace Squidex.Domain.Apps.Read.Contents.GraphQL.Types
{
public sealed class ContentGraphType : ObjectGraphType<IContentEntity>
{
private static readonly IFieldResolver DataResolver =
new FuncFieldResolver<IContentEntity, NamedContentData>(c => c.Source.Data);
public ContentGraphType(Schema schema, IGraphQLContext context)
public ContentGraphType(Schema schema, IGraphQLContext graphQLContext)
{
var schemaName = schema.Properties.Label.WithFallback(schema.Name);
Field("id", x => x.Id)
Name = $"{schema.Name.ToPascalCase()}Dto";
Field("id", x => x.Id.ToString())
.Description($"The id of the {schemaName} content.");
Field("version", x => x.Version)
.Description($"The version of the {schemaName} content.");
Field("created", x => x.Created)
Field("created", x => x.Created.ToDateTimeUtc())
.Description($"The date and time when the {schemaName} content has been created.");
Field("createdBy", x => x.CreatedBy.ToString())
.Description($"The user that has created the {schemaName} content.");
Field("lastModified", x => x.LastModified.ToString())
Field("lastModified", x => x.LastModified.ToDateTimeUtc())
.Description($"The date and time when the {schemaName} content has been modified last.");
Field("lastModified", x => x.LastModified.ToString())
Field("lastModifiedBy", x => x.LastModifiedBy.ToString())
.Description($"The user that has updated the {schemaName} content last.");
AddField(new FieldType
{
Name = "data",
Resolver = DataResolver,
ResolvedType = new SchemaDataGraphType(schema, context),
Description = $"The version of the {schemaName} content."
ResolvedType = new NonNullGraphType(new ContentDataGraphType(schema, graphQLContext)),
Description = $"The data of the {schemaName} content."
});
Description = $"The structure of a {schemaName} content type.";
}
}
}

192
src/Squidex.Domain.Apps.Read/Contents/GraphQL/Types/ContentQueryType.cs

@ -0,0 +1,192 @@
// ==========================================================================
// GraphModelType.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
using System;
using System.Collections.Generic;
using System.Linq;
using GraphQL.Resolvers;
using GraphQL.Types;
using Squidex.Domain.Apps.Read.Schemas;
using Squidex.Infrastructure;
namespace Squidex.Domain.Apps.Read.Contents.GraphQL.Types
{
public sealed class ContentQueryType : ObjectGraphType
{
public ContentQueryType(IGraphQLContext graphQLContext, IEnumerable<ISchemaEntity> schemaEntities)
{
AddAssetFind(graphQLContext);
AddAssetsQuery(graphQLContext);
foreach (var schemaEntity in schemaEntities)
{
var schemaName = schemaEntity.Schema.Properties.Label.WithFallback(schemaEntity.Schema.Name);
var schemaType = new ContentGraphType(schemaEntity.Schema, graphQLContext);
AddContentFind(schemaEntity, schemaName, schemaType);
AddContentQuery(schemaEntity, schemaType, schemaName);
}
Description = "The app queries.";
}
private void AddAssetFind(IGraphQLContext graphQLContext)
{
AddField(new FieldType
{
Name = "findAsset",
Arguments = new QueryArguments
{
new QueryArgument(typeof(StringGraphType))
{
Name = "id",
Description = "The id of the asset.",
DefaultValue = string.Empty
}
},
ResolvedType = graphQLContext.GetAssetType(),
Resolver = new FuncFieldResolver<object>(c =>
{
var context = (QueryContext)c.UserContext;
var contentId = Guid.Parse(c.GetArgument("id", Guid.Empty.ToString()));
return context.FindAssetAsync(contentId);
}),
Description = "Find an asset by id."
});
}
private void AddContentFind(ISchemaEntity schemaEntity, string schemaName, IGraphType schemaType)
{
AddField(new FieldType
{
Name = $"find{schemaEntity.Name.ToPascalCase()}",
Arguments = new QueryArguments
{
new QueryArgument(typeof(StringGraphType))
{
Name = "id",
Description = $"The id of the {schemaName} content.",
DefaultValue = string.Empty
}
},
ResolvedType = schemaType,
Resolver = new FuncFieldResolver<object>(c =>
{
var context = (QueryContext)c.UserContext;
var contentId = Guid.Parse(c.GetArgument("id", Guid.Empty.ToString()));
return context.FindContentAsync(schemaEntity.Id, contentId);
}),
Description = $"Find an {schemaName} content by id."
});
}
private void AddAssetsQuery(IGraphQLContext graphQLContext)
{
AddField(new FieldType
{
Name = "queryAssets",
Arguments = new QueryArguments
{
new QueryArgument(typeof(IntGraphType))
{
Name = "top",
Description = "Optional number of assets to take.",
DefaultValue = 20
},
new QueryArgument(typeof(IntGraphType))
{
Name = "skip",
Description = "Optional number of assets to skip.",
DefaultValue = 0
},
new QueryArgument(typeof(StringGraphType))
{
Name = "search",
Description = "Optional query.",
DefaultValue = string.Empty
}
},
ResolvedType = new ListGraphType(new NonNullGraphType(graphQLContext.GetAssetType())),
Resolver = new FuncFieldResolver<object>(c =>
{
var context = (QueryContext)c.UserContext;
var argTop = c.GetArgument("top", 20);
var argSkip = c.GetArgument("skip", 0);
var argQuery = c.GetArgument("query", string.Empty);
return context.QueryAssetsAsync(argQuery, argSkip, argTop);
}),
Description = "Query assets items."
});
}
private void AddContentQuery(ISchemaEntity schemaEntity, IGraphType schemaType, string schemaName)
{
AddField(new FieldType
{
Name = $"query{schemaEntity.Name.ToPascalCase()}",
Arguments = new QueryArguments
{
new QueryArgument(typeof(IntGraphType))
{
Name = "top",
Description = "Optional number of contents to take.",
DefaultValue = 20
},
new QueryArgument(typeof(IntGraphType))
{
Name = "skip",
Description = "Optional number of contents to skip.",
DefaultValue = 0
},
new QueryArgument(typeof(StringGraphType))
{
Name = "filter",
Description = "Optional OData filter.",
DefaultValue = string.Empty
},
new QueryArgument(typeof(StringGraphType))
{
Name = "search",
Description = "Optional OData full text search.",
DefaultValue = string.Empty
},
new QueryArgument(typeof(StringGraphType))
{
Name = "orderby",
Description = "Optional OData order definition.",
DefaultValue = string.Empty
}
},
ResolvedType = new ListGraphType(new NonNullGraphType(schemaType)),
Resolver = new FuncFieldResolver<object>(c =>
{
var context = (QueryContext)c.UserContext;
var contentQuery = BuildODataQuery(c);
return context.QueryContentsAsync(schemaEntity.Id, contentQuery);
}),
Description = $"Query {schemaName} content items."
});
}
private static string BuildODataQuery(ResolveFieldContext c)
{
var odataQuery = "?" +
string.Join("&",
c.Arguments
.Select(x => new { x.Key, Value = x.Value.ToString() }).Where(x => !string.IsNullOrWhiteSpace(x.Value))
.Select(x => $"${x.Key}={x.Value}"));
return odataQuery;
}
}
}

6
src/Squidex.Domain.Apps.Read/Contents/Repositories/IContentRepository.cs

@ -15,12 +15,12 @@ namespace Squidex.Domain.Apps.Read.Contents.Repositories
{
public interface IContentRepository
{
Task<IReadOnlyList<IContentEntity>> QueryAsync(Guid schemaId, bool nonPublished, HashSet<Guid> ids, string odataQuery, IAppEntity appEntity);
Task<IReadOnlyList<IContentEntity>> QueryAsync(IAppEntity appEntity, Guid schemaId, bool nonPublished, HashSet<Guid> ids, string odataQuery);
Task<IReadOnlyList<Guid>> QueryNotFoundAsync(Guid appId, Guid schemaId, IList<Guid> contentIds);
Task<long> CountAsync(Guid schemaId, bool nonPublished, HashSet<Guid> ids, string odataQuery, IAppEntity appEntity);
Task<long> CountAsync(IAppEntity appEntity, Guid schemaId, bool nonPublished, HashSet<Guid> ids, string odataQuery);
Task<IContentEntity> FindContentAsync(Guid schemaId, Guid id, IAppEntity appEntity);
Task<IContentEntity> FindContentAsync(IAppEntity appEntity, Guid schemaId, Guid id);
}
}

37
src/Squidex.Domain.Apps.Read/GraphQl/ContentDataGraphType.cs

@ -1,37 +0,0 @@
// ==========================================================================
// SchemaGraphType.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
using GraphQL.Resolvers;
using GraphQL.Types;
using Squidex.Domain.Apps.Core.Contents;
using Squidex.Infrastructure;
using Schema = Squidex.Domain.Apps.Core.Schemas.Schema;
namespace Squidex.Domain.Apps.Read.GraphQl
{
public sealed class ContentDataGraphType : ObjectGraphType<NamedContentData>
{
private static readonly IFieldResolver FieldResolver =
new FuncFieldResolver<NamedContentData, ContentFieldData>(c => c.Source.GetOrDefault(c.FieldName));
public ContentDataGraphType(Schema schema, IGraphQLContext context)
{
foreach (var field in schema.Fields)
{
var fieldName = field.Name;
AddField(new FieldType
{
Name = fieldName,
Resolver = FieldResolver,
ResolvedType = new SchemaFieldGraphType(field, context),
});
}
}
}
}

36
src/Squidex.Domain.Apps.Read/GraphQl/ContentFieldGraphType.cs

@ -1,36 +0,0 @@
// ==========================================================================
// SchemaGraphType.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
using GraphQL.Types;
using Squidex.Domain.Apps.Core.Contents;
using Squidex.Domain.Apps.Core.Schemas;
namespace Squidex.Domain.Apps.Read.GraphQl
{
public sealed class ContentFieldGraphType : ObjectGraphType<ContentFieldData>
{
public ContentFieldGraphType(Field field, IGraphQLContext context)
{
var partition = context.ResolvePartition(field.Paritioning);
foreach (var partitionItem in partition)
{
var fieldInfo = context.GetGraphType(field);
AddField(new FieldType
{
Name = partitionItem.Key,
Resolver = fieldInfo.Resolver,
ResolvedType = fieldInfo.ResolveType,
Description = field.RawProperties.Hints
});
}
}
}
}

94
src/Squidex.Domain.Apps.Read/GraphQl/GraphQLContext.cs

@ -1,94 +0,0 @@
// ==========================================================================
// GraphQLContext.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using GraphQL.Resolvers;
using GraphQL.Types;
using Squidex.Domain.Apps.Core;
using Squidex.Domain.Apps.Core.Contents;
using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Domain.Apps.Read.Contents;
using Squidex.Infrastructure;
using Schema = Squidex.Domain.Apps.Core.Schemas.Schema;
namespace Squidex.Domain.Apps.Read.GraphQl
{
public sealed class GraphQLContext : IGraphQLContext
{
private readonly PartitionResolver partitionResolver;
private readonly Dictionary<Type, (IGraphType ResolveType, IFieldResolver Resolver)> fieldInfos;
public GraphQLContext(PartitionResolver partitionResolver)
{
Guard.NotNull(partitionResolver, nameof(partitionResolver));
this.partitionResolver = partitionResolver;
var defaultResolver =
new FuncFieldResolver<ContentFieldData, object>(c => c.Source.GetOrDefault(c.FieldName));
fieldInfos = new Dictionary<Type, (IGraphType ResolveType, IFieldResolver Resolver)>
{
{
typeof(StringField),
(new StringGraphType(), defaultResolver)
},
{
typeof(BooleanField),
(new BooleanGraphType(), defaultResolver)
},
{
typeof(NumberField),
(new FloatGraphType(), defaultResolver)
},
{
typeof(DateTimeField),
(new FloatGraphType(), defaultResolver)
},
{
typeof(JsonField),
(new ObjectGraphType(), defaultResolver)
},
{
typeof(GeolocationField),
(new ObjectGraphType(), defaultResolver)
},
{
typeof(AssetsField),
(new ListGraphType<StringGraphType>(), defaultResolver)
},
{
typeof(ReferencesField),
(new ListGraphType<StringGraphType>(), defaultResolver)
}
};
}
public IGraphType GetSchemaListType(Schema schema)
{
throw new NotImplementedException();
}
public IGraphType GetSchemaListType(Guid schemaId)
{
throw new NotImplementedException();
}
public IFieldPartitioning ResolvePartition(Partitioning key)
{
return partitionResolver(key);
}
public (IGraphType ResolveType, IFieldResolver Resolver) GetGraphType(Field field)
{
return fieldInfos[field.GetType()];
}
}
}

6
src/Squidex/Config/Domain/ReadModule.cs

@ -16,6 +16,7 @@ using Squidex.Domain.Apps.Read.Apps.Services;
using Squidex.Domain.Apps.Read.Apps.Services.Implementations;
using Squidex.Domain.Apps.Read.Contents;
using Squidex.Domain.Apps.Read.Contents.Edm;
using Squidex.Domain.Apps.Read.Contents.GraphQL;
using Squidex.Domain.Apps.Read.History;
using Squidex.Domain.Apps.Read.Schemas;
using Squidex.Domain.Apps.Read.Schemas.Services;
@ -87,6 +88,11 @@ namespace Squidex.Config.Domain
builder.RegisterType<EdmModelBuilder>()
.AsSelf()
.SingleInstance();
builder.RegisterType<CachingGraphQLInvoker>()
.As<IGraphQLInvoker>()
.AsSelf()
.InstancePerDependency();
}
}
}

2
src/Squidex/Config/Domain/StoreMongoDbModule.cs

@ -15,6 +15,7 @@ using MongoDB.Driver;
using Squidex.Domain.Apps.Read.Apps.Repositories;
using Squidex.Domain.Apps.Read.Apps.Services.Implementations;
using Squidex.Domain.Apps.Read.Assets.Repositories;
using Squidex.Domain.Apps.Read.Contents.GraphQL;
using Squidex.Domain.Apps.Read.Contents.Repositories;
using Squidex.Domain.Apps.Read.History.Repositories;
using Squidex.Domain.Apps.Read.MongoDb.Apps;
@ -170,6 +171,7 @@ namespace Squidex.Config.Domain
builder.Register(c =>
new CompoundEventConsumer(
c.Resolve<MongoSchemaRepository>(),
c.Resolve<CachingGraphQLInvoker>(),
c.Resolve<CachingSchemaProvider>()))
.As<IEventConsumer>()
.AsSelf()

37
src/Squidex/Controllers/ContentApi/ContentsController.cs

@ -15,8 +15,10 @@ using Microsoft.Extensions.Primitives;
using NSwag.Annotations;
using Squidex.Controllers.ContentApi.Models;
using Squidex.Domain.Apps.Core.Contents;
using Squidex.Domain.Apps.Read.Contents.GraphQL;
using Squidex.Domain.Apps.Read.Contents.Repositories;
using Squidex.Domain.Apps.Read.Schemas;
using Squidex.Domain.Apps.Read.Schemas.Repositories;
using Squidex.Domain.Apps.Read.Schemas.Services;
using Squidex.Domain.Apps.Write.Contents.Commands;
using Squidex.Infrastructure.CQRS.Commands;
@ -33,15 +35,40 @@ namespace Squidex.Controllers.ContentApi
{
private readonly ISchemaProvider schemas;
private readonly IContentRepository contentRepository;
private readonly IGraphQLInvoker graphQL;
public ContentsController(ICommandBus commandBus, ISchemaProvider schemas, IContentRepository contentRepository)
public ContentsController(
ICommandBus commandBus,
ISchemaProvider schemas,
IContentRepository contentRepository,
IGraphQLInvoker graphQL)
: base(commandBus)
{
this.graphQL = graphQL;
this.schemas = schemas;
this.contentRepository = contentRepository;
}
[HttpGet]
[Route("content/{app}/graphql")]
[ApiCosts(2)]
public async Task<IActionResult> GetGraphQL([FromQuery] GraphQLQuery query)
{
var result = await graphQL.QueryAsync(App, query);
return Ok(result);
}
[HttpPost]
[Route("content/{app}/graphql")]
[ApiCosts(2)]
public async Task<IActionResult> PostGraphQL([FromBody] GraphQLQuery query)
{
var result = await graphQL.QueryAsync(App, query);
return Ok(result);
}
[HttpGet]
[Route("content/{app}/{name}")]
[ApiCosts(2)]
@ -64,8 +91,8 @@ namespace Squidex.Controllers.ContentApi
var query = Request.QueryString.ToString();
var taskForItems = contentRepository.QueryAsync(schemaEntity.Id, nonPublished, idsList, query, App);
var taskForCount = contentRepository.CountAsync(schemaEntity.Id, nonPublished, idsList, query, App);
var taskForItems = contentRepository.QueryAsync(App, schemaEntity.Id, nonPublished, idsList, query);
var taskForCount = contentRepository.CountAsync(App, schemaEntity.Id, nonPublished, idsList, query);
await Task.WhenAll(taskForItems, taskForCount);
@ -100,7 +127,7 @@ namespace Squidex.Controllers.ContentApi
return NotFound();
}
var entity = await contentRepository.FindContentAsync(schemaEntity.Id, id, App);
var entity = await contentRepository.FindContentAsync(App, schemaEntity.Id, id);
if (entity == null)
{

4
src/Squidex/app/app.routes.ts

@ -70,6 +70,10 @@ export const routes: Routes = [
{
path: 'settings',
loadChildren: './features/settings/module#SqxFeatureSettingsModule'
},
{
path: 'api',
loadChildren: './features/api/module#SqxFeatureApiModule'
}
]
}

33
src/Squidex/app/features/api/api-area.component.html

@ -0,0 +1,33 @@
<sqx-title message="{app} | Settings" parameter1="app" value1="{{appName() | async}}"></sqx-title>
<sqx-panel theme="dark" panelWidth="12rem">
<div class="panel-header">
<div class="panel-title-row">
<h3 class="panel-title">API</h3>
</div>
<a class="panel-close" sqxParentLink isLazyLoaded="true">
<i class="icon-close"></i>
</a>
</div>
<div class="panel-main">
<div class="panel-content">
<ul class="nav flex-column nav-dark">
<li class="nav-item">
<a class="nav-link" routerLink="graphql" routerLinkActive="active">
GraphQL
<i class="icon-angle-right"></i>
</a>
</li>
<li class="nav-item">
<a class="nav-link">
REST
</a>
</li>
</ul>
</div>
</div>
</sqx-panel>
<router-outlet></router-outlet>

12
src/Squidex/app/features/api/api-area.component.scss

@ -0,0 +1,12 @@
@import '_vars';
@import '_mixins';
.nav-link {
position: relative;
padding-top: .6rem;
padding-bottom: .6rem;
}
.icon-angle-right {
@include absolute(14px, 2rem, auto, auto);
}

26
src/Squidex/app/features/api/api-area.component.ts

@ -0,0 +1,26 @@
/*
* Squidex Headless CMS
*
* @license
* Copyright (c) Sebastian Stehle. All rights reserved
*/
import { Component } from '@angular/core';
import {
AppComponentBase,
AppsStoreService,
NotificationService
} from 'shared';
@Component({
selector: 'sqx-api-area',
styleUrls: ['./api-area.component.scss'],
templateUrl: './api-area.component.html'
})
export class ApiAreaComponent extends AppComponentBase {
constructor(apps: AppsStoreService, notifications: NotificationService
) {
super(notifications, apps);
}
}

10
src/Squidex/app/features/api/declarations.ts

@ -0,0 +1,10 @@
/*
* Squidex Headless CMS
*
* @license
* Copyright (c) Sebastian Stehle. All rights reserved
*/
export * from './pages/graphql/graphql-page.component';
export * from './api-area.component';

9
src/Squidex/app/features/api/index.ts

@ -0,0 +1,9 @@
/*
* Squidex Headless CMS
*
* @license
* Copyright (c) Sebastian Stehle. All rights reserved
*/
export * from './declarations';
export * from './module';

50
src/Squidex/app/features/api/module.ts

@ -0,0 +1,50 @@
/*
* Squidex Headless CMS
*
* @license
* Copyright (c) Sebastian Stehle. All rights reserved
*/
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { DndModule } from 'ng2-dnd';
import {
SqxFrameworkModule,
SqxSharedModule
} from 'shared';
import {
ApiAreaComponent,
GraphQLPageComponent
} from './declarations';
const routes: Routes = [
{
path: '',
component: ApiAreaComponent,
children: [
{
path: ''
},
{
path: 'graphql',
component: GraphQLPageComponent
}
]
}
];
@NgModule({
imports: [
DndModule,
SqxFrameworkModule,
SqxSharedModule,
RouterModule.forChild(routes)
],
declarations: [
ApiAreaComponent,
GraphQLPageComponent
]
})
export class SqxFeatureApiModule { }

5
src/Squidex/app/features/api/pages/graphql/graphql-page.component.html

@ -0,0 +1,5 @@
<sqx-title message="{app} | API | GraphQL" parameter1="app" value1="{{appName() | async}}"></sqx-title>
<sqx-panel panelWidth="60rem" expand="true">
<div #graphiQLContainer></div>
</sqx-panel>

12
src/Squidex/app/features/api/pages/graphql/graphql-page.component.scss

@ -0,0 +1,12 @@
@import '_vars';
@import '_mixins';
@import '~graphiql/graphiql';
.graphiql-container {
@include absolute(0, 0, 0, 0);
}
.graphiql-container > * {
box-sizing: content-box;
}

55
src/Squidex/app/features/api/pages/graphql/graphql-page.component.ts

@ -0,0 +1,55 @@
/*
* Squidex Headless CMS
*
* @license
* Copyright (c) Sebastian Stehle. All rights reserved
*/
import { Component, ElementRef, OnInit, ViewChild, ViewEncapsulation } from '@angular/core';
import * as React from 'react';
import * as ReactDOM from 'react-dom';
const GraphiQL = require('graphiql');
import {
ApiUrlConfig,
AppComponentBase,
AppsStoreService,
AuthService,
NotificationService
} from 'shared';
@Component({
selector: 'sqx-graphql-page',
styleUrls: ['./graphql-page.component.scss'],
templateUrl: './graphql-page.component.html',
encapsulation: ViewEncapsulation.None
})
export class GraphQLPageComponent extends AppComponentBase implements OnInit {
@ViewChild('graphiQLContainer')
public graphiQLContainer: ElementRef;
constructor(apps: AppsStoreService, notifications: NotificationService,
private readonly authService: AuthService,
private readonly apiUrl: ApiUrlConfig
) {
super(notifications, apps);
}
public ngOnInit() {
ReactDOM.render(
React.createElement(GraphiQL, {
fetcher: (params: any) => this.request(params)
}),
this.graphiQLContainer.nativeElement
);
}
private request(params: any) {
return this.appNameOnce()
.switchMap(app => this.authService.authPost(this.apiUrl.buildUrl(`api/content/${app}/graphql`), params).map(r => r.json()))
.toPromise();
}
}

72
src/Squidex/app/framework/angular/panel-container.directive.ts

@ -5,50 +5,84 @@
* Copyright (c) Sebastian Stehle. All rights reserved
*/
import { Directive, ElementRef, HostListener, OnDestroy, OnInit } from '@angular/core';
import { Subscription } from 'rxjs';
import { AfterViewInit, Directive, ElementRef, HostListener, OnDestroy, Renderer } from '@angular/core';
import { PanelService } from './../services/panel.service';
import { PanelComponent } from './panel.component';
@Directive({
selector: '.panel-container'
})
export class PanelContainerDirective implements OnInit, OnDestroy {
private subscription: Subscription;
private panelsSize: number | null = null;
export class PanelContainerDirective implements AfterViewInit, OnDestroy {
private readonly panels: PanelComponent[] = [];
private isInit = false;
constructor(
private readonly element: ElementRef,
private readonly panels: PanelService
private readonly renderer: Renderer
) {
}
@HostListener('window:resize')
public onResize() {
this.resize();
this.invalidate();
}
public ngAfterViewInit() {
this.invalidate(true);
}
public ngOnDestroy() {
this.subscription.unsubscribe();
this.isInit = true;
}
public push(panel: PanelComponent) {
this.panels.push(panel);
this.invalidate();
}
public ngOnInit() {
this.subscription =
this.panels.changed.subscribe(width => {
this.panelsSize = width;
public pop() {
this.panels.splice(-1, 1);
this.resize();
});
this.invalidate();
}
private resize() {
if (!this.panelsSize) {
public invalidate(force = false) {
this.isInit = this.isInit || force;
if (!this.isInit) {
return;
}
const currentWidth = this.element.nativeElement.getBoundingClientRect().width;
const containerWidth = this.element.nativeElement.getBoundingClientRect().width;
let currentPosition = 0;
let currentLayer = this.panels.length * 10;
const last = this.panels[this.panels.length - 1];
for (let panel of this.panels) {
const panelRoot = panel.panel.nativeElement;
let width = panelRoot.getBoundingClientRect().width;
if (panel.expand && panel === last) {
width = containerWidth - currentPosition;
panel.panelWidth = width + 'px';
}
this.renderer.setElementStyle(panelRoot, 'top', '0px');
this.renderer.setElementStyle(panelRoot, 'left', currentPosition + 'px');
this.renderer.setElementStyle(panelRoot, 'bottom', '0px');
this.renderer.setElementStyle(panelRoot, 'position', 'absolute');
this.renderer.setElementStyle(panelRoot, 'z-index', currentLayer.toString());
currentPosition += width;
currentLayer -= 10;
}
const diff = this.panelsSize - currentWidth;
const diff = currentPosition - containerWidth;
if (diff > 0) {
this.element.nativeElement.scrollLeft = diff;

27
src/Squidex/app/framework/angular/panel.component.ts

@ -5,16 +5,16 @@
* Copyright (c) Sebastian Stehle. All rights reserved
*/
import { AfterViewInit, Component, ElementRef, Input, OnDestroy, Renderer, ViewChild } from '@angular/core';
import { AfterViewInit, Component, ElementRef, Input, OnDestroy, OnInit, ViewChild } from '@angular/core';
import { slideRightAnimation } from './animations';
import { PanelService } from './../services/panel.service';
import { PanelContainerDirective } from './panel-container.directive';
@Component({
selector: 'sqx-panel',
template: `
<div [style.width]="panelWidth" #panel>
<div [style.width]="panelWidth" [attr.expand]="expand" #panel>
<div class="panel panel-{{theme}}" [@slideRight]>
<ng-content></ng-content>
</div>
@ -23,30 +23,33 @@ import { PanelService } from './../services/panel.service';
slideRightAnimation
]
})
export class PanelComponent implements OnDestroy, AfterViewInit {
export class PanelComponent implements AfterViewInit, OnDestroy, OnInit {
@Input()
public theme = 'light';
@Input()
public panelWidth = '10rem';
@Input()
public expand = false;
@ViewChild('panel')
public panel: ElementRef;
constructor(
private readonly renderer: Renderer,
private readonly panels: PanelService
private readonly container: PanelContainerDirective
) {
}
public ngOnDestroy() {
this.panels.pop(this.panel.nativeElement);
this.panels.render(this.renderer);
}
public ngAfterViewInit() {
this.panels.render(this.renderer);
this.container.pop();
}
public ngOnInit() {
this.panels.push(this.panel.nativeElement);
this.container.push(this);
}
public ngAfterViewInit() {
this.container.invalidate();
}
}

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

@ -52,7 +52,6 @@ export * from './services/clipboard.service';
export * from './services/local-store.service';
export * from './services/message-bus';
export * from './services/notification.service';
export * from './services/panel.service';
export * from './services/resource-loader.service';
export * from './services/shortcut.service';
export * from './services/title.service';

2
src/Squidex/app/framework/module.ts

@ -43,7 +43,6 @@ import {
NotificationService,
PanelContainerDirective,
PanelComponent,
PanelService,
ParentLinkDirective,
PopupLinkDirective,
ProgressBarComponent,
@ -177,7 +176,6 @@ export class SqxFrameworkModule {
LocalStoreService,
MessageBus,
NotificationService,
PanelService,
ResourceLoaderService,
ShortcutService,
TitleService

84
src/Squidex/app/framework/services/panel.service.spec.ts

@ -1,84 +0,0 @@
/*
* Squidex Headless CMS
*
* @license
* Copyright (c) Sebastian Stehle. All rights reserved
*/
import { PanelService, PanelServiceFactory } from './../';
interface Styling { element: any; property: string; value: string; }
describe('PanelService', () => {
it('should instantiate from factory', () => {
const panelService = PanelServiceFactory();
expect(panelService).toBeDefined();
});
it('should instantiate', () => {
const panelService = new PanelService();
expect(panelService).toBeDefined();
});
it('should update elements with renderer service', () => {
let styles: Styling[] = [];
const renderer = {
setElementStyle: (element: any, property: string, value: string) => {
styles.push({element, property, value});
}
};
const panelService = new PanelService();
const element1 = {
getBoundingClientRect: () => {
return { width: 100 };
}
};
const element2 = {
getBoundingClientRect: () => {
return { width: 200 };
}
};
const element3 = {
getBoundingClientRect: () => {
return { width: 300 };
}
};
let numPublished = 0;
panelService.changed.subscribe(() => {
numPublished++;
});
panelService.push(element1);
panelService.push(element2);
panelService.push(element3);
styles = [];
panelService.pop(element3);
panelService.render(<any>renderer);
expect(styles).toEqual([
{ element: element1, property: 'top', value: '0px' },
{ element: element1, property: 'left', value: '0px' },
{ element: element1, property: 'bottom', value: '0px' },
{ element: element1, property: 'position', value: 'absolute' },
{ element: element1, property: 'z-index', value: '20' },
{ element: element2, property: 'top', value: '0px' },
{ element: element2, property: 'left', value: '100px' },
{ element: element2, property: 'bottom', value: '0px' },
{ element: element2, property: 'position', value: 'absolute' },
{ element: element2, property: 'z-index', value: '10' }
]);
expect(numPublished).toBe(1);
});
});

51
src/Squidex/app/framework/services/panel.service.ts

@ -1,51 +0,0 @@
/*
* Squidex Headless CMS
*
* @license
* Copyright (c) Sebastian Stehle. All rights reserved
*/
import { Injectable, Renderer } from '@angular/core';
import { Observable, Subject } from 'rxjs';
export const PanelServiceFactory = () => {
return new PanelService();
};
@Injectable()
export class PanelService {
private readonly elements: any[] = [];
private readonly changed$ = new Subject<number>();
public get changed(): Observable<number> {
return this.changed$;
}
public push(element: any) {
this.elements.push(element);
}
public pop(element: any) {
this.elements.splice(-1, 1);
}
public render(renderer: Renderer) {
let currentPosition = 0;
let currentLayer = this.elements.length * 10;
for (let element of this.elements) {
const width = element.getBoundingClientRect().width;
renderer.setElementStyle(element, 'top', '0px');
renderer.setElementStyle(element, 'left', currentPosition + 'px');
renderer.setElementStyle(element, 'bottom', '0px');
renderer.setElementStyle(element, 'position', 'absolute');
renderer.setElementStyle(element, 'z-index', currentLayer.toString());
currentPosition += width;
currentLayer -= 10;
}
this.changed$.next(currentPosition);
}
}

5
src/Squidex/app/shell/pages/app/left-menu.component.html

@ -25,5 +25,10 @@
<i class="nav-icon icon-settings"></i> <div class="nav-text">Settings</div>
</a>
</li>
<li class="nav-item" *ngIf="permission !== 'Editor'">
<a class="nav-link" routerLink="api" routerLinkActive="active">
<i class="nav-icon icon-earth"></i> <div class="nav-text">API</div>
</a>
</li>
</ul>
</div>

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

@ -9,56 +9,24 @@
<link rel="stylesheet" href="style.css"></head>
<body>
<div class="bgc1 clearfix">
<h1 class="mhmm mvm"><span class="fgc1">Font Name:</span> icomoon <small class="fgc1">(Glyphs:&nbsp;69)</small></h1>
<h1 class="mhmm mvm"><span class="fgc1">Font Name:</span> icomoon <small class="fgc1">(Glyphs:&nbsp;67)</small></h1>
</div>
<div class="clearfix mhl ptl">
<h1 class="mvm mtn fgc1">Grid Size: 16</h1>
<div class="glyph fs1">
<div class="clearfix bshadow0 pbs">
<span class="icon-elapsed">
<span class="icon-earth">
</span>
<span class="mls"> icon-elapsed</span>
<span class="mls"> icon-earth</span>
</div>
<fieldset class="fs0 size1of1 clearfix hidden-false">
<input type="text" readonly value="e943" class="unit size1of2" />
<input type="text" maxlength="1" readonly value="&#xe943;" class="unitRight size1of2 talign-right" />
<input type="text" readonly value="e9ca" class="unit size1of2" />
<input type="text" maxlength="1" readonly value="&#xe9ca;" class="unitRight size1of2 talign-right" />
</fieldset>
<div class="fs0 bshadow0 clearfix hidden-true">
<span class="unit pvs fgc1">liga: </span>
<input type="text" readonly value="" class="liga unitRight" />
</div>
</div>
<div class="glyph fs1">
<div class="clearfix bshadow0 pbs">
<span class="icon-timeout">
</span>
<span class="mls"> icon-timeout</span>
</div>
<fieldset class="fs0 size1of1 clearfix hidden-false">
<input type="text" readonly value="e944" class="unit size1of2" />
<input type="text" maxlength="1" readonly value="&#xe944;" class="unitRight size1of2 talign-right" />
</fieldset>
<div class="fs0 bshadow0 clearfix hidden-true">
<span class="unit pvs fgc1">liga: </span>
<input type="text" readonly value="" class="liga unitRight" />
</div>
</div>
<div class="glyph fs1">
<div class="clearfix bshadow0 pbs">
<span class="icon-checkmark">
</span>
<span class="mls"> icon-checkmark</span>
</div>
<fieldset class="fs0 size1of1 clearfix hidden-false">
<input type="text" readonly value="e942" class="unit size1of2" />
<input type="text" maxlength="1" readonly value="&#xe942;" class="unitRight size1of2 talign-right" />
</fieldset>
<div class="fs0 bshadow0 clearfix hidden-true">
<span class="unit pvs fgc1">liga: </span>
<input type="text" readonly value="" class="liga unitRight" />
<input type="text" readonly value="earth, globe2" class="liga unitRight" />
</div>
</div>
<div class="glyph fs1">

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

Binary file not shown.

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

@ -73,7 +73,5 @@
<glyph unicode="&#xe93f;" glyph-name="webhook" d="M524.792 917.333c-133.751 0-243.208-108.454-243.208-241 0-72.755 34.012-137.024 85.833-181.292l-115.167-187.458c-3.016 0.305-5.946 0.875-9.042 0.875-49.392 0-89.625-39.845-89.625-88.792s40.233-88.792 89.625-88.792c49.392 0 89.583 39.845 89.583 88.792 0 17.952-5.505 34.638-14.792 48.625l152.208 247.708-32.458 19.833c-47.549 29.077-79.333 80.999-79.333 140.5 0 91.263 74.283 164.875 166.375 164.875s166.417-73.612 166.417-164.875c0-14.491-1.921-28.493-5.458-41.875l74.292-19.292c5.167 19.548 7.958 40.089 7.958 61.167 0 132.546-109.457 241-243.208 241zM524.792 765.125c-49.392 0-89.583-39.845-89.583-88.792s40.191-88.792 89.583-88.792c1.932 0 3.765 0.422 5.667 0.542l136.375-242.5 33.5 18.417c23.855 13.109 51.195 20.583 80.458 20.583 92.092 0 166.417-73.654 166.417-164.917s-74.324-164.875-166.417-164.875c-52.606 0-99.199 24.144-129.792 61.875l-59.917-47.708c44.569-54.969 113.29-90.292 189.708-90.292 133.751 0 243.208 108.454 243.208 241s-109.457 241-243.208 241c-28.895 0-55.96-6.577-81.792-15.833l-101.333 180.208c10.44 14.518 16.75 32.153 16.75 51.292 0 48.947-40.233 88.792-89.625 88.792zM182.333 453.042c-104.909-26.881-182.333-121.59-182.333-233.375 0-132.546 109.457-241 243.208-241 120.157 0 216.178 89.065 235.375 202.958h221.625c14.445-29.878 44.991-50.75 80.583-50.75 49.392 0 89.625 39.845 89.625 88.792s-40.233 88.792-89.625 88.792c-35.592 0-66.138-20.831-80.583-50.708h-290.625v-38.083c0-91.263-74.283-164.875-166.375-164.875s-166.417 73.612-166.417 164.875c0 76.963 53.266 141.381 124.792 159.708l-19.25 73.667z" />
<glyph unicode="&#xe940;" glyph-name="microsoft" d="M0.35 448l-0.35 312.074 384 52.144v-364.218zM448 821.518l511.872 74.482v-448h-511.872zM959.998 384l-0.126-448-511.872 72.016v375.984zM384 16.164l-383.688 52.594-0.020 315.242h383.708z" />
<glyph unicode="&#xe941;" glyph-name="github" d="M512 960c-282.88 0-512-229.248-512-512 0-226.24 146.688-418.112 350.080-485.76 25.6-4.8 35.008 11.008 35.008 24.64 0 12.16-0.448 44.352-0.64 87.040-142.464-30.912-172.48 68.672-172.48 68.672-23.296 59.136-56.96 74.88-56.96 74.88-46.4 31.744 3.584 31.104 3.584 31.104 51.392-3.584 78.4-52.736 78.4-52.736 45.696-78.272 119.872-55.68 149.12-42.56 4.608 33.088 17.792 55.68 32.448 68.48-113.728 12.8-233.216 56.832-233.216 252.992 0 55.872 19.84 101.568 52.672 137.408-5.76 12.928-23.040 64.96 4.48 135.488 0 0 42.88 13.76 140.8-52.48 40.96 11.392 84.48 17.024 128 17.28 43.52-0.256 87.040-5.888 128-17.28 97.28 66.24 140.16 52.48 140.16 52.48 27.52-70.528 10.24-122.56 5.12-135.488 32.64-35.84 52.48-81.536 52.48-137.408 0-196.672-119.68-240-233.6-252.608 17.92-15.36 34.56-46.72 34.56-94.72 0-68.48-0.64-123.52-0.64-140.16 0-13.44 8.96-29.44 35.2-24.32 204.864 67.136 351.424 259.136 351.424 485.056 0 282.752-229.248 512-512 512z" />
<glyph unicode="&#xe942;" glyph-name="checkmark" d="M864 832l-480-480-224 224-160-160 384-384 640 640z" />
<glyph unicode="&#xe943;" glyph-name="elapsed" d="M512.002 766.788v65.212h128v64c0 35.346-28.654 64-64.002 64h-191.998c-35.346 0-64-28.654-64-64v-64h128v-65.212c-214.798-16.338-384-195.802-384-414.788 0-229.75 186.25-416 416-416s416 186.25 416 416c0 218.984-169.202 398.448-384 414.788zM706.276 125.726c-60.442-60.44-140.798-93.726-226.274-93.726s-165.834 33.286-226.274 93.726c-60.44 60.44-93.726 140.8-93.726 226.274s33.286 165.834 93.726 226.274c58.040 58.038 134.448 91.018 216.114 93.548l-21.678-314.020c-1.86-26.29 12.464-37.802 31.836-37.802s33.698 11.512 31.836 37.802l-21.676 314.022c81.666-2.532 158.076-35.512 216.116-93.55 60.44-60.44 93.726-140.8 93.726-226.274s-33.286-165.834-93.726-226.274z" />
<glyph unicode="&#xe944;" glyph-name="timeout" d="M512 832c-247.424 0-448-200.576-448-448s200.576-448 448-448 448 200.576 448 448-200.576 448-448 448zM512 24c-198.824 0-360 161.178-360 360 0 198.824 161.176 360 360 360 198.822 0 360-161.176 360-360 0-198.822-161.178-360-360-360zM934.784 672.826c16.042 28.052 25.216 60.542 25.216 95.174 0 106.040-85.96 192-192 192-61.818 0-116.802-29.222-151.92-74.596 131.884-27.236 245.206-105.198 318.704-212.578v0zM407.92 885.404c-35.116 45.374-90.102 74.596-151.92 74.596-106.040 0-192-85.96-192-192 0-34.632 9.174-67.122 25.216-95.174 73.5 107.38 186.822 185.342 318.704 212.578zM512 384v256h-64v-320h256v64z" />
<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" />
</font></defs></svg>

Before

Width:  |  Height:  |  Size: 58 KiB

After

Width:  |  Height:  |  Size: 58 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.

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

@ -4,102 +4,35 @@
{
"icon": {
"paths": [
"M512.002 193.212v-65.212h128v-64c0-35.346-28.654-64-64.002-64h-191.998c-35.346 0-64 28.654-64 64v64h128v65.212c-214.798 16.338-384 195.802-384 414.788 0 229.75 186.25 416 416 416s416-186.25 416-416c0-218.984-169.202-398.448-384-414.788zM706.276 834.274c-60.442 60.44-140.798 93.726-226.274 93.726s-165.834-33.286-226.274-93.726c-60.44-60.44-93.726-140.8-93.726-226.274s33.286-165.834 93.726-226.274c58.040-58.038 134.448-91.018 216.114-93.548l-21.678 314.020c-1.86 26.29 12.464 37.802 31.836 37.802s33.698-11.512 31.836-37.802l-21.676-314.022c81.666 2.532 158.076 35.512 216.116 93.55 60.44 60.44 93.726 140.8 93.726 226.274s-33.286 165.834-93.726 226.274z"
],
"attrs": [
{}
"M512 0c-282.77 0-512 229.23-512 512s229.23 512 512 512 512-229.23 512-512-229.23-512-512-512zM512 960.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.26v-96c0-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 32v192c0 12.122 6.848 23.202 17.69 28.622l110.31 55.156v187.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.628v-77.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"
],
"attrs": [],
"isMulticolor": false,
"isMulticolor2": false,
"tags": [
"stopwatch",
"time",
"speed",
"meter",
"chronometer"
"earth",
"globe",
"language",
"web",
"internet",
"sphere",
"planet"
],
"defaultCode": 59850,
"grid": 16
},
"attrs": [
{}
],
"attrs": [],
"properties": {
"order": 1,
"id": 2,
"ligatures": "earth, globe2",
"name": "earth",
"id": 202,
"order": 91,
"prevSize": 32,
"code": 59715,
"name": "elapsed"
"code": 59850
},
"setIdx": 1,
"setId": 2,
"iconIdx": 0
},
{
"icon": {
"paths": [
"M512 128c-247.424 0-448 200.576-448 448s200.576 448 448 448 448-200.576 448-448-200.576-448-448-448zM512 936c-198.824 0-360-161.178-360-360 0-198.824 161.176-360 360-360 198.822 0 360 161.176 360 360 0 198.822-161.178 360-360 360zM934.784 287.174c16.042-28.052 25.216-60.542 25.216-95.174 0-106.040-85.96-192-192-192-61.818 0-116.802 29.222-151.92 74.596 131.884 27.236 245.206 105.198 318.704 212.578v0zM407.92 74.596c-35.116-45.374-90.102-74.596-151.92-74.596-106.040 0-192 85.96-192 192 0 34.632 9.174 67.122 25.216 95.174 73.5-107.38 186.822-185.342 318.704-212.578z",
"M512 576v-256h-64v320h256v-64z"
],
"attrs": [
{},
{}
],
"isMulticolor": false,
"isMulticolor2": false,
"tags": [
"alarm",
"time",
"clock"
],
"grid": 16
},
"attrs": [
{},
{}
],
"properties": {
"order": 2,
"id": 1,
"prevSize": 32,
"code": 59716,
"name": "timeout"
},
"setIdx": 1,
"setId": 2,
"iconIdx": 1
},
{
"icon": {
"paths": [
"M864 128l-480 480-224-224-160 160 384 384 640-640z"
],
"attrs": [
{}
],
"isMulticolor": false,
"isMulticolor2": false,
"tags": [
"checkmark",
"tick",
"correct",
"accept",
"ok"
],
"grid": 16
},
"attrs": [
{}
],
"properties": {
"order": 1,
"id": 0,
"name": "checkmark",
"prevSize": 32,
"code": 59714
},
"setIdx": 1,
"setId": 2,
"iconIdx": 2
"setIdx": 0,
"setId": 4,
"iconIdx": 202
},
{
"icon": {
@ -128,7 +61,7 @@
"code": 59712,
"name": "microsoft"
},
"setIdx": 2,
"setIdx": 3,
"setId": 1,
"iconIdx": 39
},
@ -158,7 +91,7 @@
"code": 59707,
"name": "google"
},
"setIdx": 2,
"setIdx": 3,
"setId": 1,
"iconIdx": 40
},
@ -188,7 +121,7 @@
"code": 59699,
"name": "unlocked"
},
"setIdx": 2,
"setIdx": 3,
"setId": 1,
"iconIdx": 41
},
@ -220,7 +153,7 @@
"code": 59700,
"name": "lock"
},
"setIdx": 2,
"setIdx": 3,
"setId": 1,
"iconIdx": 42
},
@ -259,7 +192,7 @@
"code": 59694,
"name": "reset"
},
"setIdx": 2,
"setIdx": 3,
"setId": 1,
"iconIdx": 43
},
@ -289,7 +222,7 @@
"code": 59695,
"name": "pause"
},
"setIdx": 2,
"setIdx": 3,
"setId": 1,
"iconIdx": 44
},
@ -319,7 +252,7 @@
"code": 59696,
"name": "play"
},
"setIdx": 2,
"setIdx": 3,
"setId": 1,
"iconIdx": 45
},
@ -354,7 +287,7 @@
"code": 59693,
"name": "settings2"
},
"setIdx": 2,
"setIdx": 3,
"setId": 1,
"iconIdx": 46
},
@ -391,7 +324,7 @@
"prevSize": 32,
"code": 59650
},
"setIdx": 2,
"setIdx": 3,
"setId": 1,
"iconIdx": 47
},
@ -420,7 +353,7 @@
"prevSize": 32,
"code": 59711
},
"setIdx": 2,
"setIdx": 3,
"setId": 1,
"iconIdx": 0
},
@ -450,7 +383,7 @@
"prevSize": 32,
"code": 59713
},
"setIdx": 2,
"setIdx": 3,
"setId": 1,
"iconIdx": 1
},
@ -479,7 +412,7 @@
"prevSize": 32,
"code": 59652
},
"setIdx": 2,
"setIdx": 3,
"setId": 1,
"iconIdx": 2
},
@ -508,7 +441,7 @@
"prevSize": 32,
"code": 59653
},
"setIdx": 2,
"setIdx": 3,
"setId": 1,
"iconIdx": 3
},
@ -537,7 +470,7 @@
"prevSize": 32,
"code": 59654
},
"setIdx": 2,
"setIdx": 3,
"setId": 1,
"iconIdx": 4
},
@ -566,7 +499,7 @@
"prevSize": 32,
"code": 59655
},
"setIdx": 2,
"setIdx": 3,
"setId": 1,
"iconIdx": 5
},
@ -595,7 +528,7 @@
"prevSize": 32,
"code": 59656
},
"setIdx": 2,
"setIdx": 3,
"setId": 1,
"iconIdx": 6
},
@ -624,7 +557,7 @@
"prevSize": 32,
"code": 59657
},
"setIdx": 2,
"setIdx": 3,
"setId": 1,
"iconIdx": 7
},
@ -653,7 +586,7 @@
"prevSize": 32,
"code": 59658
},
"setIdx": 2,
"setIdx": 3,
"setId": 1,
"iconIdx": 8
},
@ -682,7 +615,7 @@
"prevSize": 32,
"code": 59659
},
"setIdx": 2,
"setIdx": 3,
"setId": 1,
"iconIdx": 9
},
@ -711,7 +644,7 @@
"prevSize": 32,
"code": 59660
},
"setIdx": 2,
"setIdx": 3,
"setId": 1,
"iconIdx": 10
},
@ -740,7 +673,7 @@
"prevSize": 32,
"code": 59661
},
"setIdx": 2,
"setIdx": 3,
"setId": 1,
"iconIdx": 11
},
@ -769,7 +702,7 @@
"prevSize": 32,
"code": 59662
},
"setIdx": 2,
"setIdx": 3,
"setId": 1,
"iconIdx": 12
},
@ -798,7 +731,7 @@
"prevSize": 32,
"code": 59663
},
"setIdx": 2,
"setIdx": 3,
"setId": 1,
"iconIdx": 13
},
@ -827,7 +760,7 @@
"prevSize": 32,
"code": 59664
},
"setIdx": 2,
"setIdx": 3,
"setId": 1,
"iconIdx": 14
},
@ -856,7 +789,7 @@
"prevSize": 32,
"code": 59665
},
"setIdx": 2,
"setIdx": 3,
"setId": 1,
"iconIdx": 15
},
@ -885,7 +818,7 @@
"prevSize": 32,
"code": 59666
},
"setIdx": 2,
"setIdx": 3,
"setId": 1,
"iconIdx": 16
},
@ -914,7 +847,7 @@
"prevSize": 32,
"code": 59667
},
"setIdx": 2,
"setIdx": 3,
"setId": 1,
"iconIdx": 17
},
@ -943,7 +876,7 @@
"prevSize": 32,
"code": 59668
},
"setIdx": 2,
"setIdx": 3,
"setId": 1,
"iconIdx": 18
},
@ -972,7 +905,7 @@
"prevSize": 32,
"code": 59669
},
"setIdx": 2,
"setIdx": 3,
"setId": 1,
"iconIdx": 19
},
@ -1001,7 +934,7 @@
"prevSize": 32,
"code": 59670
},
"setIdx": 2,
"setIdx": 3,
"setId": 1,
"iconIdx": 20
},
@ -1030,7 +963,7 @@
"prevSize": 32,
"code": 59671
},
"setIdx": 2,
"setIdx": 3,
"setId": 1,
"iconIdx": 21
},
@ -1059,7 +992,7 @@
"prevSize": 32,
"code": 59672
},
"setIdx": 2,
"setIdx": 3,
"setId": 1,
"iconIdx": 22
},
@ -1088,7 +1021,7 @@
"prevSize": 32,
"code": 59673
},
"setIdx": 2,
"setIdx": 3,
"setId": 1,
"iconIdx": 23
},
@ -1117,7 +1050,7 @@
"prevSize": 32,
"code": 59674
},
"setIdx": 2,
"setIdx": 3,
"setId": 1,
"iconIdx": 24
},
@ -1146,7 +1079,7 @@
"prevSize": 32,
"code": 59675
},
"setIdx": 2,
"setIdx": 3,
"setId": 1,
"iconIdx": 25
},
@ -1175,7 +1108,7 @@
"prevSize": 32,
"code": 59676
},
"setIdx": 2,
"setIdx": 3,
"setId": 1,
"iconIdx": 26
},
@ -1204,7 +1137,7 @@
"prevSize": 32,
"code": 59677
},
"setIdx": 2,
"setIdx": 3,
"setId": 1,
"iconIdx": 27
},
@ -1233,7 +1166,7 @@
"prevSize": 32,
"code": 59678
},
"setIdx": 2,
"setIdx": 3,
"setId": 1,
"iconIdx": 28
},
@ -1262,7 +1195,7 @@
"prevSize": 32,
"code": 59679
},
"setIdx": 2,
"setIdx": 3,
"setId": 1,
"iconIdx": 29
},
@ -1291,7 +1224,7 @@
"prevSize": 32,
"code": 59680
},
"setIdx": 2,
"setIdx": 3,
"setId": 1,
"iconIdx": 30
},
@ -1320,7 +1253,7 @@
"prevSize": 32,
"code": 59681
},
"setIdx": 2,
"setIdx": 3,
"setId": 1,
"iconIdx": 31
},
@ -1349,7 +1282,7 @@
"prevSize": 32,
"code": 59682
},
"setIdx": 2,
"setIdx": 3,
"setId": 1,
"iconIdx": 32
},
@ -1378,7 +1311,7 @@
"prevSize": 32,
"code": 59683
},
"setIdx": 2,
"setIdx": 3,
"setId": 1,
"iconIdx": 33
},
@ -1407,7 +1340,7 @@
"prevSize": 32,
"code": 59684
},
"setIdx": 2,
"setIdx": 3,
"setId": 1,
"iconIdx": 34
},
@ -1436,7 +1369,7 @@
"prevSize": 32,
"code": 59685
},
"setIdx": 2,
"setIdx": 3,
"setId": 1,
"iconIdx": 35
},
@ -1465,7 +1398,7 @@
"prevSize": 32,
"code": 59686
},
"setIdx": 2,
"setIdx": 3,
"setId": 1,
"iconIdx": 36
},
@ -1494,7 +1427,7 @@
"prevSize": 32,
"code": 59687
},
"setIdx": 2,
"setIdx": 3,
"setId": 1,
"iconIdx": 37
},
@ -1523,7 +1456,7 @@
"prevSize": 32,
"code": 59688
},
"setIdx": 2,
"setIdx": 3,
"setId": 1,
"iconIdx": 38
},
@ -1552,7 +1485,7 @@
"code": 59710,
"name": "download"
},
"setIdx": 2,
"setIdx": 3,
"setId": 1,
"iconIdx": 48
},
@ -1581,7 +1514,7 @@
"code": 59705,
"name": "control-RichText"
},
"setIdx": 2,
"setIdx": 3,
"setId": 1,
"iconIdx": 49
},
@ -1611,7 +1544,7 @@
"code": 59709,
"name": "bug"
},
"setIdx": 2,
"setIdx": 3,
"setId": 1,
"iconIdx": 50
},
@ -1640,7 +1573,7 @@
"prevSize": 28,
"code": 59704
},
"setIdx": 2,
"setIdx": 3,
"setId": 1,
"iconIdx": 51
},
@ -1669,7 +1602,7 @@
"prevSize": 28,
"code": 59702
},
"setIdx": 2,
"setIdx": 3,
"setId": 1,
"iconIdx": 52
},
@ -1698,7 +1631,7 @@
"prevSize": 28,
"code": 59703
},
"setIdx": 2,
"setIdx": 3,
"setId": 1,
"iconIdx": 53
},
@ -1728,7 +1661,7 @@
"code": 59697,
"name": "angle-right"
},
"setIdx": 2,
"setIdx": 3,
"setId": 1,
"iconIdx": 54
},
@ -1757,7 +1690,7 @@
"prevSize": 28,
"code": 59698
},
"setIdx": 2,
"setIdx": 3,
"setId": 1,
"iconIdx": 55
},
@ -1787,7 +1720,7 @@
"code": 59689,
"name": "caret-right"
},
"setIdx": 2,
"setIdx": 3,
"setId": 1,
"iconIdx": 56
},
@ -1817,7 +1750,7 @@
"code": 59690,
"name": "caret-left"
},
"setIdx": 2,
"setIdx": 3,
"setId": 1,
"iconIdx": 57
},
@ -1847,7 +1780,7 @@
"code": 59691,
"name": "caret-up"
},
"setIdx": 2,
"setIdx": 3,
"setId": 1,
"iconIdx": 58
},
@ -1877,7 +1810,7 @@
"code": 59692,
"name": "caret-down"
},
"setIdx": 2,
"setIdx": 3,
"setId": 1,
"iconIdx": 59
},
@ -1907,7 +1840,7 @@
"code": 59651,
"name": "angle-up"
},
"setIdx": 2,
"setIdx": 3,
"setId": 1,
"iconIdx": 60
},
@ -1937,7 +1870,7 @@
"code": 59648,
"name": "angle-down"
},
"setIdx": 2,
"setIdx": 3,
"setId": 1,
"iconIdx": 61
},
@ -1967,7 +1900,7 @@
"code": 59649,
"name": "angle-left"
},
"setIdx": 2,
"setIdx": 3,
"setId": 1,
"iconIdx": 62
},
@ -1996,7 +1929,7 @@
"code": 59708,
"name": "info"
},
"setIdx": 2,
"setIdx": 3,
"setId": 1,
"iconIdx": 63
},
@ -2026,7 +1959,7 @@
"code": 59706,
"name": "control-Stars"
},
"setIdx": 2,
"setIdx": 3,
"setId": 1,
"iconIdx": 64
},
@ -2059,7 +1992,7 @@
"code": 59701,
"name": "browser"
},
"setIdx": 2,
"setIdx": 3,
"setId": 1,
"iconIdx": 65
}

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

@ -1,10 +1,10 @@
@font-face {
font-family: 'icomoon';
src: url('fonts/icomoon.eot?3aocbn');
src: url('fonts/icomoon.eot?3aocbn#iefix') format('embedded-opentype'),
url('fonts/icomoon.ttf?3aocbn') format('truetype'),
url('fonts/icomoon.woff?3aocbn') format('woff'),
url('fonts/icomoon.svg?3aocbn#icomoon') format('svg');
src: url('fonts/icomoon.eot?oi12nb');
src: url('fonts/icomoon.eot?oi12nb#iefix') format('embedded-opentype'),
url('fonts/icomoon.ttf?oi12nb') format('truetype'),
url('fonts/icomoon.woff?oi12nb') format('woff'),
url('fonts/icomoon.svg?oi12nb#icomoon') format('svg');
font-weight: normal;
font-style: normal;
}
@ -24,14 +24,8 @@
-moz-osx-font-smoothing: grayscale;
}
.icon-elapsed:before {
content: "\e943";
}
.icon-timeout:before {
content: "\e944";
}
.icon-checkmark:before {
content: "\e942";
.icon-earth:before {
content: "\e9ca";
}
.icon-microsoft:before {
content: "\e940";

6
src/Squidex/app/theme/theme.scss

@ -1,13 +1,13 @@
@import '_bootstrap-vars.scss';
// Bootstrap
@import './../../node_modules/bootstrap/scss/bootstrap.scss';
@import '~bootstrap/scss/bootstrap.scss';
// Pikaday
@import './../../node_modules/pikaday/css/pikaday.css';
@import '~pikaday/css/pikaday.css';
// Drag and Drop
@import './../../node_modules/ng2-dnd/bundles/style.css';
@import '~ng2-dnd/bundles/style.css';
// Bootstrap Overrides
@import '_bootstrap.scss';

7
src/Squidex/package.json

@ -24,17 +24,20 @@
"@angular/platform-browser": "4.2.5",
"@angular/platform-browser-dynamic": "4.2.5",
"@angular/router": "4.2.5",
"angular2-chartjs": "^0.2.0",
"angular-progress-http": "0.5.1",
"angular2-chartjs": "^0.2.0",
"babel-polyfill": "6.23.0",
"bootstrap": "4.0.0-alpha.6",
"core-js": "2.4.1",
"graphiql": "^0.11.2",
"moment": "2.18.1",
"mousetrap": "1.6.1",
"ng2-dnd": "4.2.0",
"oidc-client": "1.3.0",
"pikaday": "1.6.1",
"progressbar.js": "1.0.1",
"react": "^15.6.1",
"react-dom": "^15.6.1",
"redoc": "1.16.1",
"rxjs": "5.4.2",
"zone.js": "0.8.12"
@ -47,6 +50,8 @@
"@types/jasmine": "2.5.43",
"@types/mousetrap": "1.5.33",
"@types/node": "7.0.5",
"@types/react": "^15.0.35",
"@types/react-dom": "^15.5.1",
"angular2-router-loader": "0.3.5",
"angular2-template-loader": "0.6.2",
"awesome-typescript-loader": "3.2.1",

Loading…
Cancel
Save