Browse Source

Merge branch 'master' of github.com:Squidex/squidex into performance-improvements

# Conflicts:
#	backend/src/Squidex.Data.EntityFramework/Squidex.Data.EntityFramework.csproj
#	backend/src/Squidex.Data.MongoDb/Squidex.Data.MongoDb.csproj
#	backend/src/Squidex.Domain.Apps.Core.Model/Squidex.Domain.Apps.Core.Model.csproj
#	backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/Internal/JintExtensions.cs
#	backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/JintScriptEngine.cs
#	backend/src/Squidex.Domain.Apps.Core.Operations/Squidex.Domain.Apps.Core.Operations.csproj
#	backend/src/Squidex.Infrastructure/Squidex.Infrastructure.csproj
#	backend/src/Squidex/Squidex.csproj
#	backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/Scripting/JintScriptEngineTests.cs
pull/1330/head
Sebastian Stehle 2 weeks ago
parent
commit
d710f84950
  1. 23
      backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/ContentWrapper/ContentFieldObject.cs
  2. 29
      backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/Internal/JintObjectConverter.cs
  3. 31
      backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/Internal/JsonMapper.cs
  4. 22
      backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/NullPropagation.cs
  5. 11
      backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/WritableContext.cs
  6. 132
      backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/Scripting/ContentDataObjectTests.cs
  7. 52
      backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/Scripting/JsonMapperTests.cs
  8. 33
      backend/tests/Squidex.Domain.Apps.Core.Tests/TestHelpers/JintHostContractVerification.cs
  9. 1
      frontend/src/app/_theme.html
  10. 4
      frontend/src/app/theme/_bootstrap.scss
  11. 1
      frontend/src/app/theme/_vars.scss
  12. 22
      frontend/src/app/theme/icomoon/demo.html
  13. BIN
      frontend/src/app/theme/icomoon/fonts/icomoon.eot
  14. 1
      frontend/src/app/theme/icomoon/fonts/icomoon.svg
  15. BIN
      frontend/src/app/theme/icomoon/fonts/icomoon.ttf
  16. BIN
      frontend/src/app/theme/icomoon/fonts/icomoon.woff
  17. 6
      frontend/src/app/theme/icomoon/icons/externaloidc.svg
  18. 2
      frontend/src/app/theme/icomoon/selection.json
  19. 13
      frontend/src/app/theme/icomoon/style.css

23
backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/ContentWrapper/ContentFieldObject.cs

@ -132,6 +132,29 @@ public sealed class ContentFieldObject : ObjectInstance
return valueProperties?.GetValueOrDefault(propertyName) ?? PropertyDescriptor.Undefined;
}
protected override OwnPropertyProbe ProbeOwnProperty(JsValue property)
{
// Answers whether a key exists without converting its value, which reading the property would do.
// Used by "in", hasOwnProperty, Object.keys, spread and JSON.stringify. Must give the same answer as
// GetOwnProperty above, which Jint does not check at runtime, only in tests (see
// JintHostContractVerification), so the two methods are kept identical apart from the return value.
EnsurePropertiesInitialized();
var propertyName = property.AsString();
if (propertyName.Equals("toJSON", StringComparison.OrdinalIgnoreCase))
{
return OwnPropertyProbe.Missing;
}
if (!valueProperties.TryGetValue(propertyName, out var propertyDescriptor))
{
return OwnPropertyProbe.Missing;
}
return propertyDescriptor.Enumerable ? OwnPropertyProbe.Enumerable : OwnPropertyProbe.NonEnumerable;
}
public override IEnumerable<KeyValuePair<JsValue, PropertyDescriptor>> GetOwnProperties()
{
EnsurePropertiesInitialized();

29
backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/Internal/JintObjectConverter.cs

@ -21,6 +21,29 @@ namespace Squidex.Domain.Apps.Core.Scripting.Internal;
public sealed class JintObjectConverter : IObjectConverter
{
/// <summary>
/// The types this converter handles, passed to Jint when the converter is registered.
/// </summary>
/// <remarks>
/// Without this list Jint has to offer every property of every .NET object to this converter and cannot
/// use its faster property reader for any of them. Base types and interfaces count, so
/// <see cref="IUser"/> covers all implementations. Keep the list in sync with the switch below - a type
/// that is converted but not listed here fails a test (see JintHostContractVerification). Enums are
/// missing on purpose, they are converted by Jint itself, see EnumConversion in JintScriptEngine.
/// </remarks>
public static readonly Type[] HandledTypes =
[
typeof(IUser),
typeof(ClaimsPrincipal),
typeof(ScriptVars),
typeof(JsonValue),
typeof(DomainId),
typeof(Guid),
typeof(Instant),
typeof(Status),
typeof(ContentData),
];
public static readonly JintObjectConverter Instance = new JintObjectConverter();
private JintObjectConverter()
@ -31,12 +54,6 @@ public sealed class JintObjectConverter : IObjectConverter
{
result = null!;
if (value is Enum)
{
result = value.ToString();
return true;
}
switch (value)
{
case IUser user:

31
backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/Internal/JsonMapper.cs

@ -6,7 +6,6 @@
// ==========================================================================
using System.Collections;
using System.Globalization;
using Jint;
using Jint.Native;
using Jint.Native.Object;
@ -18,10 +17,6 @@ namespace Squidex.Domain.Apps.Core.Scripting.Internal;
public static class JsonMapper
{
private sealed class JsonObjectInstance(Engine engine) : ObjectInstance(engine)
{
}
public static JsValue Map(JsonValue value, Engine engine)
{
switch (value.Value)
@ -33,9 +28,9 @@ public static class JsonMapper
case false:
return JsBoolean.False;
case double n:
return new JsNumber(n);
return JsNumber.Create(n);
case string s:
return new JsString(s);
return JsString.Create(s);
case JsonObject o:
return FromObject(o, engine);
case JsonArray a:
@ -58,16 +53,20 @@ public static class JsonMapper
return engine.Intrinsics.Array.Construct(target);
}
private static JsonObjectInstance FromObject(JsonObject obj, Engine engine)
private static JsObject FromObject(JsonObject obj, Engine engine)
{
var target = new JsonObjectInstance(engine);
// Objects that are created this way and have the same keys - all content items of a schema do -
// share one description of their layout, like a class. Reading a property is then a lot faster than
// with a custom ObjectInstance class, where every single object gets its own property dictionary.
var entries = new KeyValuePair<string, JsValue>[obj.Count];
var index = 0;
foreach (var (key, value) in obj)
{
target.Set(key, Map(value, engine));
entries[index++] = new KeyValuePair<string, JsValue>(key, Map(value, engine));
}
return target;
return JsObject.CreateFromEntries(engine, entries);
}
public static JsonValue Map(JsValue? value)
@ -116,11 +115,15 @@ public static class JsonMapper
if (value is JsArray a)
{
var result = new JsonArray((int)a.Length);
var length = a.Length;
var result = new JsonArray((int)length);
for (var i = 0; i < a.Length; i++)
// The indexer reads the array storage directly. The old version converted the index to a string
// and did a full property lookup for every element.
for (var i = 0u; i < length; i++)
{
result.Add(Map(a.Get(i.ToString(CultureInfo.InvariantCulture))));
result.Add(Map(a[i]));
}
return result;

22
backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/NullPropagation.cs

@ -14,8 +14,30 @@ namespace Squidex.Domain.Apps.Core.Scripting;
public sealed class NullPropagation : IReferenceResolver
{
/// <summary>
/// The cases this resolver actually handles.
/// </summary>
/// <remarks>
/// Without this list Jint has to assume that we want to see every property read and turns off its read
/// caches for the whole engine. But <see cref="TryPropertyReference"/> only ever does something when the
/// value is null or undefined, so the other cases can be left to Jint. Behavior does not change: for a
/// case that is not listed here Jint behaves as if no resolver was registered at all.
/// </remarks>
public const ReferenceResolverInterests Interests =
ReferenceResolverInterests.NullishPropertyBase |
ReferenceResolverInterests.UnresolvableReference |
ReferenceResolverInterests.NonCallableCallee;
public static readonly NullPropagation Instance = new NullPropagation();
/// <summary>
/// Called when a name does not exist, so that reading an unknown variable does not throw.
/// </summary>
/// <remarks>
/// The returned base is not <c>undefined</c> here but an internal Jint marker string that reads
/// <c>[[Unresolvable]]</c>. That is what scripts have always seen, so it is kept as it is and covered by
/// a test. Returning <c>undefined</c> would be nicer, but would change behavior for existing scripts.
/// </remarks>
public bool TryUnresolvableReference(Engine engine, Reference reference, out JsValue value)
{
value = reference.Base;

11
backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/WritableContext.cs

@ -1,4 +1,4 @@
// ==========================================================================
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
@ -8,6 +8,7 @@
using Jint;
using Jint.Native;
using Jint.Native.Object;
using Jint.Runtime.Descriptors;
namespace Squidex.Domain.Apps.Core.Scripting;
@ -20,9 +21,15 @@ internal sealed class WritableContext : ObjectInstance
{
this.vars = vars;
// Adds the value, but runs the conversion only when the script reads it for the first time. Most
// scripts use a few of these variables and some of them are expensive, e.g. the user variable walks
// and groups all claims. The properties themselves are added right away, so key order, enumeration
// and "in" checks stay the same.
foreach (var (key, item) in vars)
{
base.Set(key, FromObject(engine, item), this);
SetOwnProperty(key, PropertyDescriptor.CreateLazy(
(Engine: engine, Item: item),
static state => FromObject(state.Engine, state.Item)));
}
}

132
backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/Scripting/ContentDataObjectTests.cs

@ -409,6 +409,138 @@ public class ContentDataObjectTests
ExecuteScript([], script);
}
[Fact]
public void Should_answer_in_operator_for_field_values()
{
const string script = @"
('iv' in data.string) + ',' + ('unknown' in data.string);
";
var actual = EvaluateScript(CreateContent(), script);
Assert.Equal("true,false", actual);
}
[Fact]
public void Should_answer_has_own_property_for_field_values()
{
const string script = @"
data.string.hasOwnProperty('iv') + ',' + data.string.hasOwnProperty('unknown');
";
var actual = EvaluateScript(CreateContent(), script);
Assert.Equal("true,false", actual);
}
[Fact]
public void Should_list_field_value_keys()
{
const string script = @"
Object.keys(data.string).join(',');
";
var actual = EvaluateScript(CreateContent(), script);
Assert.Equal("iv,de", actual);
}
[Fact]
public void Should_report_field_values_as_enumerable()
{
const string script = @"
data.string.propertyIsEnumerable('iv') + ',' + data.string.propertyIsEnumerable('unknown');
";
var actual = EvaluateScript(CreateContent(), script);
Assert.Equal("true,false", actual);
}
[Fact]
public void Should_stringify_field_values()
{
const string script = @"
JSON.stringify(data.string);
";
var actual = EvaluateScript(CreateContent(), script);
Assert.Equal("{\"iv\":\"1\",\"de\":\"2\"}", actual);
}
[Fact]
public void Should_stringify_content_data()
{
const string script = @"
JSON.stringify(data);
";
var actual = EvaluateScript(CreateContent(), script);
Assert.Equal("{\"string\":{\"iv\":\"1\",\"de\":\"2\"},\"number\":{\"iv\":42}}", actual);
}
[Fact]
public void Should_spread_field_values()
{
const string script = @"
JSON.stringify({ ...data.string });
";
var actual = EvaluateScript(CreateContent(), script);
Assert.Equal("{\"iv\":\"1\",\"de\":\"2\"}", actual);
}
[Fact]
public void Should_not_see_deleted_field_values()
{
const string script = @"
delete data.string.de;
('de' in data.string) + ',' + Object.keys(data.string).join(',');
";
var actual = EvaluateScript(CreateContent(), script);
Assert.Equal("false,iv", actual);
}
[Fact]
public void Should_see_added_field_values()
{
const string script = @"
data.string.en = '3';
('en' in data.string) + ',' + Object.keys(data.string).join(',');
";
var actual = EvaluateScript(CreateContent(), script);
Assert.Equal("true,iv,de,en", actual);
}
private static ContentData CreateContent()
{
return
new ContentData()
.AddField("string",
new ContentFieldData()
.AddInvariant("1")
.AddLocalized("de", "2"))
.AddField("number",
new ContentFieldData()
.AddInvariant(42));
}
private static object? EvaluateScript(ContentData original, string script)
{
var engine = new Engine(o => o.Strict());
engine.SetValue("data", new ContentDataObject(engine, original));
return engine.Evaluate(script).ToObject();
}
private static ContentData ExecuteScript(ContentData original, string script)
{
var engine = new Engine(o => o.Strict());

52
backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/Scripting/JsonMapperTests.cs

@ -0,0 +1,52 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using Jint;
using Jint.Native.Object;
using Squidex.Domain.Apps.Core.Scripting.Internal;
using Squidex.Infrastructure.Json.Objects;
namespace Squidex.Domain.Apps.Core.Operations.Scripting;
public class JsonMapperTests
{
[Fact]
public void Should_map_json_objects_into_a_shared_shape()
{
var engine = new Engine(o => o.Strict());
var mapped = (ObjectInstance)JsonMapper.Map(CreateJson(), engine);
var nested = (ObjectInstance)mapped.Get("nested");
// Sharing the layout is what makes reading properties of many content items fast. It only affects
// performance and never behavior, which is why it is asserted here: building these objects with a
// custom ObjectInstance class again would silently undo it and no other test would notice.
Assert.True(engine.Advanced.HasSharedShape(mapped));
Assert.True(engine.Advanced.HasSharedShape(nested));
}
[Fact]
public void Should_share_the_shape_between_objects_of_the_same_shape()
{
var engine = new Engine(o => o.Strict());
var first = (ObjectInstance)JsonMapper.Map(CreateJson(), engine);
var second = (ObjectInstance)JsonMapper.Map(CreateJson(), engine);
Assert.True(engine.Advanced.HasSharedShape(first));
Assert.True(engine.Advanced.HasSharedShape(second));
}
private static JsonValue CreateJson()
{
return JsonValue.Create(
new JsonObject()
.Add("name", JsonValue.Create("squidex"))
.Add("count", JsonValue.Create(3))
.Add("nested", JsonValue.Create(new JsonObject().Add("flag", JsonValue.True))));
}
}

33
backend/tests/Squidex.Domain.Apps.Core.Tests/TestHelpers/JintHostContractVerification.cs

@ -0,0 +1,33 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System.Runtime.CompilerServices;
namespace Squidex.Domain.Apps.Core.TestHelpers;
/// <summary>
/// Turns on Jint's self checks for this test assembly.
/// </summary>
/// <remarks>
/// Our ContentWrapper classes and the object converter implement Jint extension points where Jint relies on
/// our answers being consistent, without checking them - checking would cost as much as the shortcut saves.
/// A mistake there is silent in production: a key can disappear from Object.keys, or a converted type can be
/// skipped. With this switch on Jint verifies the answers and throws on the first mismatch, so a mistake
/// fails a test instead.
/// <para>
/// The switch has to be set before the first Jint type is used, which is what the module initializer
/// guarantees. It stays off in production, where the checks would only cost performance.
/// </para>
/// </remarks>
internal static class JintHostContractVerification
{
[ModuleInitializer]
internal static void Enable()
{
AppContext.SetSwitch("Jint.EnableHostContractVerification", true);
}
}

1
frontend/src/app/_theme.html

@ -214,6 +214,7 @@
<button class="btn btn-twitter"><i class="icon-twitter icon-decent"></i> Twitter</button>
<button class="btn btn-microsoft"><i class="icon-microsoft icon-decent"></i> Microsoft</button>
<button class="btn btn-github"><i class="icon-github icon-decent"></i> Github</button>
<button class="btn btn-externaloidc"><i class="icon-externaloidc icon-decent"></i> OIDC</button>
</div>
</div>

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

@ -382,6 +382,10 @@ a {
@include button-variant($color-extern-twitter, $color-extern-twitter);
}
&-externaloidc {
@include button-variant($color-extern-oidc, $color-extern-oidc);
}
// Special radio button.
&-radio {
& {

1
frontend/src/app/theme/_vars.scss

@ -21,6 +21,7 @@ $color-extern-github: #353535;
$color-extern-google: #d34836;
$color-extern-microsoft: #004185;
$color-extern-twitter: #1da1f2;
$color-extern-oidc: #526484;
$color-theme-brand: #3389ff;
$color-theme-brand-dark: #3284f4;

22
frontend/src/app/theme/icomoon/demo.html

@ -9,10 +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;155)</small></h1>
<h1 class="mhmm mvm"><span class="fgc1">Font Name:</span> icomoon <small class="fgc1">(Glyphs:&nbsp;156)</small></h1>
</div>
<div class="clearfix mhl ptl">
<h1 class="mvm mtn fgc1">Grid Size: 24</h1>
<div class="glyph fs1">
<div class="clearfix bshadow0 pbs">
<span class="icon-externaloidc"></span>
<span class="mls"> icon-externaloidc</span>
</div>
<fieldset class="fs0 size1of1 clearfix hidden-false">
<input type="text" readonly value="e906" class="unit size1of2" />
<input type="text" maxlength="1" readonly value="&#xe906;" 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-check-circle"></span>
@ -1561,7 +1575,7 @@
</div>
<div class="glyph fs3">
<div class="clearfix bshadow0 pbs">
<span class="icon-bin3"></span>
<span class="icon-delete"></span>
<span class="mls"> icon-delete</span>
</div>
<fieldset class="fs0 size1of1 clearfix hidden-false">
@ -1575,7 +1589,7 @@
</div>
<div class="glyph fs3">
<div class="clearfix bshadow0 pbs">
<span class="icon-bin3"></span>
<span class="icon-bin"></span>
<span class="mls"> icon-bin</span>
</div>
<fieldset class="fs0 size1of1 clearfix hidden-false">
@ -2264,7 +2278,7 @@
</div>
<div class="glyph fs4">
<div class="clearfix bshadow0 pbs">
<span class="icon-bin3"></span>
<span class="icon-bin2"></span>
<span class="mls"> icon-bin2</span>
</div>
<fieldset class="fs0 size1of1 clearfix hidden-false">

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

Binary file not shown.

1
frontend/src/app/theme/icomoon/fonts/icomoon.svg

@ -13,6 +13,7 @@
<glyph unicode="&#xe903;" glyph-name="angle-up" horiz-adv-x="658" d="M614.286 274.286c0-4.571-2.286-9.714-5.714-13.143l-28.571-28.571c-3.429-3.429-8-5.714-13.143-5.714-4.571 0-9.714 2.286-13.143 5.714l-224.571 224.571-224.571-224.571c-3.429-3.429-8.571-5.714-13.143-5.714s-9.714 2.286-13.143 5.714l-28.571 28.571c-3.429 3.429-5.714 8.571-5.714 13.143s2.286 9.714 5.714 13.143l266.286 266.286c3.429 3.429 8.571 5.714 13.143 5.714s9.714-2.286 13.143-5.714l266.286-266.286c3.429-3.429 5.714-8.571 5.714-13.143z" />
<glyph unicode="&#xe904;" glyph-name="activity, history, time" d="M512 836.267c-200.4 0-366.954-144.072-402.4-334.2-0.031-0.165-0.069-0.335-0.1-0.5-2.974-16.061-4.76-32.441-5.8-49.1-0.017-0.271-0.084-0.529-0.1-0.8 0.019-0.004 0.080 0.004 0.1 0-0.503-8.31-1.3-16.564-1.3-25 0-226.202 183.398-409.6 409.6-409.6 208.165 0 379.707 155.44 405.8 356.5 0.004 0.033-0.004 0.067 0 0.1 1.94 14.978 3.124 30.16 3.4 45.6 0.044 2.487 0.4 4.903 0.4 7.4 0 226.202-183.398 409.6-409.6 409.6zM512 785.067c185.461 0 337.902-140.924 356.4-321.5-35.181 21.812-84.232 39.9-151.6 39.9-85.35 0-140.891-41.606-194.6-81.9-49.152-36.864-95.55-71.7-163.8-71.7-86.067 0-135.862 54.67-175.9 98.6-9.001 9.901-17.11 17.483-25.4 25.3 23.131 175.603 172.981 311.3 354.9 311.3zM716.8 452.267c77.828 0 125.173-28.221 152.2-52.8-13.96-185.173-168.254-331.2-357-331.2-190.097 0-345.175 148.14-357.2 335.2 41.826-45.372 102.577-104.8 203.6-104.8 85.35 0 140.891 41.606 194.6 81.9 49.152 36.915 95.55 71.7 163.8 71.7z" />
<glyph unicode="&#xe905;" glyph-name="add, plus" d="M512 938.667c-35.392 0-64-28.608-64-64v-384h-384c-35.392 0-64-28.608-64-64s28.608-64 64-64h384v-384c0-35.392 28.608-64 64-64s64 28.608 64 64v384h384c35.392 0 64 28.608 64 64s-28.608 64-64 64h-384v384c0 35.392-28.608 64-64 64z" />
<glyph unicode="&#xe906;" glyph-name="externaloidc" d="M768 426.667c-25.6 0-42.667-17.067-42.667-42.667v-256c0-25.6-17.067-42.667-42.667-42.667h-469.333c-25.6 0-42.667 17.067-42.667 42.667v469.333c0 25.6 17.067 42.667 42.667 42.667h256c25.6 0 42.667 17.067 42.667 42.667s-17.067 42.667-42.667 42.667h-256c-72.533 0-128-55.467-128-128v-469.333c0-72.533 55.467-128 128-128h469.333c72.533 0 128 55.467 128 128v256c0 25.6-17.067 42.667-42.667 42.667zM934.4 827.734c-4.267 8.533-12.8 17.067-21.333 21.333-4.267 4.267-12.8 4.267-17.067 4.267h-256c-25.6 0-42.667-17.067-42.667-42.667s17.067-42.667 42.667-42.667h153.6l-396.8-396.8c-17.067-17.067-17.067-42.667 0-59.733 8.533-8.533 17.067-12.8 29.867-12.8s21.333 4.267 29.867 12.8l396.8 396.8v-153.6c0-25.6 17.067-42.667 42.667-42.667s42.667 17.067 42.667 42.667v256c0 4.267 0 12.8-4.267 17.067z" />
<glyph unicode="&#xe908;" glyph-name="close" d="M601.024 426.667l276.736-276.736c24.512-24.576 24.512-64.384 0-89.024-24.64-24.576-64.384-24.576-89.024 0l-276.736 276.736-276.736-276.736c-24.512-24.576-64.384-24.576-89.024 0-24.512 24.64-24.512 64.448 0 89.024l276.736 276.736-276.736 276.736c-24.512 24.576-24.512 64.384 0 89.024 24.64 24.576 64.512 24.576 89.024 0l276.736-276.736 276.736 276.736c24.64 24.576 64.384 24.576 89.024 0 24.512-24.64 24.512-64.448 0-89.024l-276.736-276.736z" />
<glyph unicode="&#xe909;" glyph-name="type-References" d="M409.6 503.467h-153.6v-51.2h153.6v51.2zM409.6 605.867h-153.6v-51.2h153.6v51.2zM256 247.467h409.6v51.2h-409.6v-51.2zM409.6 708.267h-153.6v-51.2h153.6v51.2zM870.4 759.467h-51.2v51.2c0 28.262-22.938 51.2-51.2 51.2h-614.4c-28.262 0-51.2-22.938-51.2-51.2v-665.6c0-28.262 22.938-51.2 51.2-51.2h51.2v-51.2c0-28.262 22.938-51.2 51.2-51.2h614.4c28.262 0 51.2 22.938 51.2 51.2v665.6c0 28.262-22.938 51.2-51.2 51.2zM179.2 145.067c-14.157 0-25.6 11.443-25.6 25.6v614.4c0 14.131 11.443 25.6 25.6 25.6h563.2c14.157 0 25.6-11.469 25.6-25.6v-614.4c0-14.157-11.443-25.6-25.6-25.6h-563.2zM870.4 68.267c0-14.157-11.443-25.6-25.6-25.6h-563.2c-14.157 0-25.6 11.443-25.6 25.6v25.6h512c28.262 0 51.2 22.938 51.2 51.2v563.2h25.6c14.157 0 25.6-11.469 25.6-25.6v-614.4zM614.4 708.267h-102.4c-28.262 0-51.2-22.938-51.2-51.2v-153.6c0-28.262 22.938-51.2 51.2-51.2h102.4c28.262 0 51.2 22.938 51.2 51.2v153.6c0 28.262-22.938 51.2-51.2 51.2zM614.4 503.467h-102.4v153.6h102.4v-153.6zM256 349.867h409.6v51.2h-409.6v-51.2z" />
<glyph unicode="&#xe90a;" glyph-name="control-Checkbox" d="M793.6 93.867c0-14.157-11.443-25.6-25.6-25.6h-665.6c-14.131 0-25.6 11.443-25.6 25.6v665.6c0 14.157 11.469 25.6 25.6 25.6h665.6c14.157 0 25.6-11.443 25.6-25.6v-102.4h51.2v128c0 28.262-22.938 51.2-51.2 51.2h-716.8c-28.262 0-51.2-22.938-51.2-51.2v-716.8c0-28.262 22.938-51.2 51.2-51.2h716.8c28.262 0 51.2 22.938 51.2 51.2v281.6h-51.2v-256zM991.078 700.92c-9.958 9.958-26.035 9.958-35.968 0l-391.91-391.91-238.31 238.31c-9.958 9.958-26.061 9.958-35.942 0-9.958-9.907-9.958-26.010 0-35.942l254.874-254.874c0.461-0.538 0.614-1.203 1.126-1.69 5.043-5.018 11.674-7.475 18.278-7.373 6.605-0.102 13.235 2.355 18.278 7.373 0.512 0.512 0.666 1.178 1.126 1.69l408.448 408.474c9.933 9.933 9.933 26.035 0 35.942z" />

Before

Width:  |  Height:  |  Size: 120 KiB

After

Width:  |  Height:  |  Size: 121 KiB

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

Binary file not shown.

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

Binary file not shown.

6
frontend/src/app/theme/icomoon/icons/externaloidc.svg

@ -0,0 +1,6 @@
<!-- Generated by IcoMoon.io -->
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
<title>externaloidc</title>
<path d="M18 12c-0.6 0-1 0.4-1 1v6c0 0.6-0.4 1-1 1h-11c-0.6 0-1-0.4-1-1v-11c0-0.6 0.4-1 1-1h6c0.6 0 1-0.4 1-1s-0.4-1-1-1h-6c-1.7 0-3 1.3-3 3v11c0 1.7 1.3 3 3 3h11c1.7 0 3-1.3 3-3v-6c0-0.6-0.4-1-1-1z"></path>
<path d="M21.9 2.6c-0.1-0.2-0.3-0.4-0.5-0.5-0.1-0.1-0.3-0.1-0.4-0.1h-6c-0.6 0-1 0.4-1 1s0.4 1 1 1h3.6l-9.3 9.3c-0.4 0.4-0.4 1 0 1.4 0.2 0.2 0.4 0.3 0.7 0.3s0.5-0.1 0.7-0.3l9.3-9.3v3.6c0 0.6 0.4 1 1 1s1-0.4 1-1v-6c0-0.1 0-0.3-0.1-0.4z"></path>
</svg>

After

Width:  |  Height:  |  Size: 617 B

2
frontend/src/app/theme/icomoon/selection.json

File diff suppressed because one or more lines are too long

13
frontend/src/app/theme/icomoon/style.css

@ -1,10 +1,10 @@
@font-face {
font-family: 'icomoon';
src: url('fonts/icomoon.eot?th15sf');
src: url('fonts/icomoon.eot?th15sf#iefix') format('embedded-opentype'),
url('fonts/icomoon.ttf?th15sf') format('truetype'),
url('fonts/icomoon.woff?th15sf') format('woff'),
url('fonts/icomoon.svg?th15sf#icomoon') format('svg');
src: url('fonts/icomoon.eot?6vhqfo');
src: url('fonts/icomoon.eot?6vhqfo#iefix') format('embedded-opentype'),
url('fonts/icomoon.ttf?6vhqfo') format('truetype'),
url('fonts/icomoon.woff?6vhqfo') format('woff'),
url('fonts/icomoon.svg?6vhqfo#icomoon') format('svg');
font-weight: normal;
font-style: normal;
font-display: block;
@ -25,6 +25,9 @@
-moz-osx-font-smoothing: grayscale;
}
.icon-externaloidc:before {
content: "\e906";
}
.icon-check-circle:before {
content: "\e999";
}

Loading…
Cancel
Save