mirror of
https://github.com/codewriter-packages/Tri-Inspector.git
synced 2025-01-22 00:08:51 -05:00
Add property validators
This commit is contained in:
parent
ff35842fe1
commit
a1da85433e
3
Editor.Extras/Validators.meta
Normal file
3
Editor.Extras/Validators.meta
Normal file
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e710031376614be5903f943dc4c4d07f
|
||||
timeCreated: 1642261774
|
25
Editor.Extras/Validators/MissingReferenceValidator.cs
Normal file
25
Editor.Extras/Validators/MissingReferenceValidator.cs
Normal file
@ -0,0 +1,25 @@
|
||||
using TriInspector;
|
||||
using TriInspector.Validators;
|
||||
using UnityEditor;
|
||||
|
||||
[assembly: RegisterTriValueValidator(typeof(MissingReferenceValidator<>))]
|
||||
|
||||
namespace TriInspector.Validators
|
||||
{
|
||||
public class MissingReferenceValidator<T> : TriValueValidator<T>
|
||||
where T : UnityEngine.Object
|
||||
{
|
||||
public override TriValidationResult Validate(TriValue<T> propertyValue)
|
||||
{
|
||||
if (propertyValue.Property.TryGetSerializedProperty(out var serializedProperty) &&
|
||||
serializedProperty.propertyType == SerializedPropertyType.ObjectReference &&
|
||||
serializedProperty.objectReferenceValue == null &&
|
||||
serializedProperty.objectReferenceInstanceIDValue != 0)
|
||||
{
|
||||
return TriValidationResult.Warning($"{propertyValue.Property.DisplayName} is missing");
|
||||
}
|
||||
|
||||
return TriValidationResult.Valid;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e6349ecb04a34792bd193b53f6cc0ca3
|
||||
timeCreated: 1642263604
|
38
Editor.Extras/Validators/RequiredValidator.cs
Normal file
38
Editor.Extras/Validators/RequiredValidator.cs
Normal file
@ -0,0 +1,38 @@
|
||||
using TriInspector.Validators;
|
||||
using TriInspector;
|
||||
|
||||
[assembly: RegisterTriAttributeValidator(typeof(RequiredValidator), ApplyOnArrayElement = true)]
|
||||
|
||||
namespace TriInspector.Validators
|
||||
{
|
||||
public class RequiredValidator : TriAttributeValidator<RequiredAttribute>
|
||||
{
|
||||
public override TriValidationResult Validate(TriProperty property)
|
||||
{
|
||||
if (property.FieldType == typeof(string))
|
||||
{
|
||||
var isNull = string.IsNullOrEmpty((string) property.Value);
|
||||
if (isNull)
|
||||
{
|
||||
var message = Attribute.Message ?? $"{property.DisplayName} is required";
|
||||
return TriValidationResult.Error(message);
|
||||
}
|
||||
}
|
||||
else if (typeof(UnityEngine.Object).IsAssignableFrom(property.FieldType))
|
||||
{
|
||||
var isNull = null == (UnityEngine.Object) property.Value;
|
||||
if (isNull)
|
||||
{
|
||||
var message = Attribute.Message ?? $"{property.DisplayName} is required";
|
||||
return TriValidationResult.Error(message);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return TriValidationResult.Error("RequiredAttribute only valid on Object and String");
|
||||
}
|
||||
|
||||
return TriValidationResult.Valid;
|
||||
}
|
||||
}
|
||||
}
|
3
Editor.Extras/Validators/RequiredValidator.cs.meta
Normal file
3
Editor.Extras/Validators/RequiredValidator.cs.meta
Normal file
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 58b713b3023343c3b571cd1b3fa7fdab
|
||||
timeCreated: 1642261781
|
@ -62,4 +62,28 @@ namespace TriInspector
|
||||
|
||||
public Type ProcessorType { get; }
|
||||
}
|
||||
|
||||
[AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)]
|
||||
public class RegisterTriValueValidatorAttribute : Attribute
|
||||
{
|
||||
public RegisterTriValueValidatorAttribute(Type validatorType)
|
||||
{
|
||||
ValidatorType = validatorType;
|
||||
}
|
||||
|
||||
public Type ValidatorType { get; }
|
||||
public bool ApplyOnArrayElement { get; set; } = true;
|
||||
}
|
||||
|
||||
[AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)]
|
||||
public class RegisterTriAttributeValidatorAttribute : Attribute
|
||||
{
|
||||
public RegisterTriAttributeValidatorAttribute(Type validatorType)
|
||||
{
|
||||
ValidatorType = validatorType;
|
||||
}
|
||||
|
||||
public Type ValidatorType { get; }
|
||||
public bool ApplyOnArrayElement { get; set; }
|
||||
}
|
||||
}
|
@ -26,6 +26,11 @@ namespace TriInspector.Elements
|
||||
element = drawers[index].CreateElementInternal(property, element);
|
||||
}
|
||||
|
||||
if (property.HasValidators)
|
||||
{
|
||||
AddChild(new TriPropertyValidationResultElement(property));
|
||||
}
|
||||
|
||||
AddChild(element);
|
||||
}
|
||||
|
||||
|
43
Editor/Elements/TriPropertyValidationResultElement.cs
Normal file
43
Editor/Elements/TriPropertyValidationResultElement.cs
Normal file
@ -0,0 +1,43 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace TriInspector.Elements
|
||||
{
|
||||
public class TriPropertyValidationResultElement : TriElement
|
||||
{
|
||||
private readonly TriProperty _property;
|
||||
private IReadOnlyList<TriValidationResult> _validationResults;
|
||||
|
||||
public TriPropertyValidationResultElement(TriProperty property)
|
||||
{
|
||||
_property = property;
|
||||
}
|
||||
|
||||
public override bool Update()
|
||||
{
|
||||
var dirty = base.Update();
|
||||
|
||||
dirty |= GenerateValidationResults();
|
||||
|
||||
return dirty;
|
||||
}
|
||||
|
||||
private bool GenerateValidationResults()
|
||||
{
|
||||
if (_property.ValidationResults == _validationResults)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
_validationResults = _property.ValidationResults;
|
||||
|
||||
RemoveAllChildren();
|
||||
|
||||
foreach (var result in _validationResults)
|
||||
{
|
||||
AddChild(new TriInfoBoxElement(result.Message, result.MessageType));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2e1128573c5f481189ccbc210cbf6b18
|
||||
timeCreated: 1642262467
|
@ -53,6 +53,21 @@ namespace TriInspector
|
||||
Profiler.EndSample();
|
||||
}
|
||||
|
||||
Profiler.BeginSample("TriInspector.RunValidation()");
|
||||
try
|
||||
{
|
||||
if (_inspector.ValidationRequired)
|
||||
{
|
||||
_inspector.ValidationRequired = false;
|
||||
|
||||
_inspector.RunValidation();
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
Profiler.EndSample();
|
||||
}
|
||||
|
||||
EditorStack.Push(this);
|
||||
Profiler.BeginSample("TriInspector.DoLayout()");
|
||||
try
|
||||
@ -65,7 +80,10 @@ namespace TriInspector
|
||||
EditorStack.Pop();
|
||||
}
|
||||
|
||||
serializedObject.ApplyModifiedProperties();
|
||||
if (serializedObject.ApplyModifiedProperties())
|
||||
{
|
||||
_inspector.RequestValidation();
|
||||
}
|
||||
|
||||
if (_inspector.RepaintRequired)
|
||||
{
|
||||
|
@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using JetBrains.Annotations;
|
||||
using TriInspector.Utilities;
|
||||
using UnityEditor;
|
||||
@ -10,11 +11,15 @@ namespace TriInspector
|
||||
{
|
||||
public sealed class TriProperty : ITriPropertyParent
|
||||
{
|
||||
private static readonly IReadOnlyList<TriValidationResult> EmptyValidationResults =
|
||||
new List<TriValidationResult>();
|
||||
|
||||
private readonly TriPropertyDefinition _definition;
|
||||
private readonly int _propertyIndex;
|
||||
private readonly ITriPropertyParent _parent;
|
||||
[CanBeNull] private readonly SerializedProperty _serializedProperty;
|
||||
private List<TriProperty> _childrenProperties;
|
||||
private List<TriValidationResult> _validationResults;
|
||||
|
||||
private GUIContent _displayNameBackingField;
|
||||
|
||||
@ -36,6 +41,9 @@ namespace TriInspector
|
||||
Update();
|
||||
}
|
||||
|
||||
[PublicAPI]
|
||||
public string DisplayName => DisplayNameContent.text;
|
||||
|
||||
[PublicAPI]
|
||||
public GUIContent DisplayNameContent
|
||||
{
|
||||
@ -137,6 +145,11 @@ namespace TriInspector
|
||||
|
||||
public ITriPropertyParent Parent => _parent;
|
||||
|
||||
public bool HasValidators => _definition.Validators.Count != 0;
|
||||
|
||||
public IReadOnlyList<TriValidationResult> ValidationResults =>
|
||||
_validationResults ?? EmptyValidationResults;
|
||||
|
||||
[PublicAPI]
|
||||
public bool IsExpanded
|
||||
{
|
||||
@ -200,7 +213,10 @@ namespace TriInspector
|
||||
public void SetValue(object value)
|
||||
{
|
||||
// save any pending changes
|
||||
PropertyTree.SerializedObject.ApplyModifiedProperties();
|
||||
if (PropertyTree.SerializedObject.ApplyModifiedProperties())
|
||||
{
|
||||
PropertyTree.RequestValidation();
|
||||
}
|
||||
|
||||
// record object state for undp
|
||||
Undo.RegisterCompleteObjectUndo(PropertyTree.TargetObjects, "Inspector");
|
||||
@ -215,6 +231,8 @@ namespace TriInspector
|
||||
// actualize
|
||||
PropertyTree.SerializedObject.Update();
|
||||
Update();
|
||||
|
||||
PropertyTree.RequestValidation();
|
||||
}
|
||||
|
||||
internal void Update()
|
||||
@ -301,6 +319,25 @@ namespace TriInspector
|
||||
}
|
||||
}
|
||||
|
||||
internal void RunValidation()
|
||||
{
|
||||
if (HasValidators)
|
||||
{
|
||||
_validationResults = _definition.Validators
|
||||
.Select(it => it.Validate(this))
|
||||
.Where(it => it.MessageType != MessageType.None)
|
||||
.ToList();
|
||||
}
|
||||
|
||||
if (_childrenProperties != null)
|
||||
{
|
||||
foreach (var childrenProperty in _childrenProperties)
|
||||
{
|
||||
childrenProperty.RunValidation();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[PublicAPI]
|
||||
public bool TryGetSerializedProperty(out SerializedProperty serializedProperty)
|
||||
{
|
||||
|
@ -17,6 +17,7 @@ namespace TriInspector
|
||||
private TriPropertyDefinition _arrayElementDefinitionBackingField;
|
||||
|
||||
private IReadOnlyList<TriCustomDrawer> _drawersBackingField;
|
||||
private IReadOnlyList<TriValidator> _validatorsBackingField;
|
||||
private IReadOnlyList<TriPropertyHideProcessor> _hideProcessorsBackingField;
|
||||
private IReadOnlyList<TriPropertyDisableProcessor> _disableProcessorsBackingField;
|
||||
|
||||
@ -117,6 +118,22 @@ namespace TriInspector
|
||||
}
|
||||
}
|
||||
|
||||
public IReadOnlyList<TriValidator> Validators
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_validatorsBackingField == null)
|
||||
{
|
||||
_validatorsBackingField = Enumerable.Empty<TriValidator>()
|
||||
.Concat(TriDrawersUtilities.CreateValueValidatorsFor(FieldType))
|
||||
.Concat(TriDrawersUtilities.CreateAttributeValidatorsFor(Attributes))
|
||||
.ToList();
|
||||
}
|
||||
|
||||
return _validatorsBackingField;
|
||||
}
|
||||
}
|
||||
|
||||
public object GetValue(TriProperty property, int targetIndex)
|
||||
{
|
||||
var parentValue = property.Parent.GetValue(targetIndex);
|
||||
|
@ -30,6 +30,8 @@ namespace TriInspector
|
||||
})
|
||||
.ToList();
|
||||
|
||||
ValidationRequired = true;
|
||||
|
||||
_mode = mode;
|
||||
_inspectorElement = new TriInspectorElement(this);
|
||||
_inspectorElement.AttachInternal();
|
||||
@ -52,6 +54,7 @@ namespace TriInspector
|
||||
public bool IsInlineEditor => (_mode & TriEditorMode.InlineEditor) != 0;
|
||||
|
||||
internal bool RepaintRequired { get; set; }
|
||||
internal bool ValidationRequired { get; set; }
|
||||
|
||||
object ITriPropertyParent.GetValue(int targetIndex) => TargetObjects[targetIndex];
|
||||
|
||||
@ -81,6 +84,14 @@ namespace TriInspector
|
||||
_inspectorElement.Update();
|
||||
}
|
||||
|
||||
internal void RunValidation()
|
||||
{
|
||||
foreach (var property in Properties)
|
||||
{
|
||||
property.RunValidation();
|
||||
}
|
||||
}
|
||||
|
||||
internal void DoLayout()
|
||||
{
|
||||
var width = EditorGUIUtility.currentViewWidth;
|
||||
@ -93,6 +104,11 @@ namespace TriInspector
|
||||
{
|
||||
RepaintRequired = true;
|
||||
}
|
||||
|
||||
public void RequestValidation()
|
||||
{
|
||||
ValidationRequired = true;
|
||||
}
|
||||
}
|
||||
|
||||
[Flags]
|
||||
|
65
Editor/TriValidator.cs
Normal file
65
Editor/TriValidator.cs
Normal file
@ -0,0 +1,65 @@
|
||||
using System;
|
||||
using JetBrains.Annotations;
|
||||
using UnityEditor;
|
||||
|
||||
namespace TriInspector
|
||||
{
|
||||
public abstract class TriValidator
|
||||
{
|
||||
internal bool ApplyOnArrayElement { get; set; }
|
||||
|
||||
[PublicAPI]
|
||||
public abstract TriValidationResult Validate(TriProperty property);
|
||||
}
|
||||
|
||||
public abstract class TriAttributeValidator : TriValidator
|
||||
{
|
||||
internal Attribute RawAttribute { get; set; }
|
||||
}
|
||||
|
||||
public abstract class TriAttributeValidator<TAttribute> : TriAttributeValidator
|
||||
where TAttribute : Attribute
|
||||
{
|
||||
[PublicAPI]
|
||||
public TAttribute Attribute => (TAttribute) RawAttribute;
|
||||
}
|
||||
|
||||
public abstract class TriValueValidator : TriValidator
|
||||
{
|
||||
}
|
||||
|
||||
public abstract class TriValueValidator<T> : TriValueValidator
|
||||
{
|
||||
public sealed override TriValidationResult Validate(TriProperty property)
|
||||
{
|
||||
return Validate(new TriValue<T>(property));
|
||||
}
|
||||
|
||||
[PublicAPI]
|
||||
public abstract TriValidationResult Validate(TriValue<T> propertyValue);
|
||||
}
|
||||
|
||||
public readonly struct TriValidationResult
|
||||
{
|
||||
public static TriValidationResult Valid => new TriValidationResult(null, MessageType.None);
|
||||
|
||||
private TriValidationResult(string message, MessageType messageType)
|
||||
{
|
||||
Message = message;
|
||||
MessageType = messageType;
|
||||
}
|
||||
|
||||
public string Message { get; }
|
||||
public MessageType MessageType { get; }
|
||||
|
||||
public static TriValidationResult Error(string error)
|
||||
{
|
||||
return new TriValidationResult(error, MessageType.Error);
|
||||
}
|
||||
|
||||
public static TriValidationResult Warning(string error)
|
||||
{
|
||||
return new TriValidationResult(error, MessageType.Warning);
|
||||
}
|
||||
}
|
||||
}
|
3
Editor/TriValidator.cs.meta
Normal file
3
Editor/TriValidator.cs.meta
Normal file
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b90ba6bdef614e9aa246f371506ea113
|
||||
timeCreated: 1642260754
|
@ -12,6 +12,8 @@ namespace TriInspector.Utilities
|
||||
private static IDictionary<Type, TriGroupDrawer> _allGroupDrawersCacheBackingField;
|
||||
private static IReadOnlyList<RegisterTriAttributeDrawerAttribute> _allAttributeDrawerTypesBackingField;
|
||||
private static IReadOnlyList<RegisterTriValueDrawerAttribute> _allValueDrawerTypesBackingField;
|
||||
private static IReadOnlyList<RegisterTriAttributeValidatorAttribute> _allAttributeValidatorTypesBackingField;
|
||||
private static IReadOnlyList<RegisterTriValueValidatorAttribute> _allValueValidatorTypesBackingField;
|
||||
private static IReadOnlyList<RegisterTriPropertyHideProcessor> _allHideProcessorTypesBackingField;
|
||||
private static IReadOnlyList<RegisterTriPropertyDisableProcessor> _allDisableProcessorTypesBackingField;
|
||||
|
||||
@ -72,6 +74,42 @@ namespace TriInspector.Utilities
|
||||
}
|
||||
}
|
||||
|
||||
public static IReadOnlyList<RegisterTriValueValidatorAttribute> AllValueValidatorTypes
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_allValueValidatorTypesBackingField == null)
|
||||
{
|
||||
_allValueValidatorTypesBackingField = (
|
||||
from asm in TriReflectionUtilities.Assemblies
|
||||
from attr in asm.GetCustomAttributes<RegisterTriValueValidatorAttribute>()
|
||||
where IsValueValidatorType(attr.ValidatorType, out _)
|
||||
select attr
|
||||
).ToList();
|
||||
}
|
||||
|
||||
return _allValueValidatorTypesBackingField;
|
||||
}
|
||||
}
|
||||
|
||||
public static IReadOnlyList<RegisterTriAttributeValidatorAttribute> AllAttributeValidatorTypes
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_allAttributeValidatorTypesBackingField == null)
|
||||
{
|
||||
_allAttributeValidatorTypesBackingField = (
|
||||
from asm in TriReflectionUtilities.Assemblies
|
||||
from attr in asm.GetCustomAttributes<RegisterTriAttributeValidatorAttribute>()
|
||||
where IsAttributeValidatorType(attr.ValidatorType, out _)
|
||||
select attr
|
||||
).ToList();
|
||||
}
|
||||
|
||||
return _allAttributeValidatorTypesBackingField;
|
||||
}
|
||||
}
|
||||
|
||||
public static IReadOnlyList<RegisterTriPropertyHideProcessor> AllHideProcessors
|
||||
{
|
||||
get
|
||||
@ -133,6 +171,16 @@ namespace TriInspector.Utilities
|
||||
return TryGetBaseGenericTargetType(type, typeof(TriAttributeDrawer<>), out attributeType);
|
||||
}
|
||||
|
||||
private static bool IsValueValidatorType(Type type, out Type valueType)
|
||||
{
|
||||
return TryGetBaseGenericTargetType(type, typeof(TriValueValidator<>), out valueType);
|
||||
}
|
||||
|
||||
private static bool IsAttributeValidatorType(Type type, out Type attributeType)
|
||||
{
|
||||
return TryGetBaseGenericTargetType(type, typeof(TriAttributeValidator<>), out attributeType);
|
||||
}
|
||||
|
||||
private static bool IsHideProcessorType(Type type, out Type attributeType)
|
||||
{
|
||||
return TryGetBaseGenericTargetType(type, typeof(TriPropertyHideProcessor<>), out attributeType);
|
||||
@ -171,6 +219,31 @@ namespace TriInspector.Utilities
|
||||
});
|
||||
}
|
||||
|
||||
public static IEnumerable<TriValueValidator> CreateValueValidatorsFor(Type valueType)
|
||||
{
|
||||
return
|
||||
from validator in AllValueValidatorTypes
|
||||
where IsValueValidatorType(validator.ValidatorType, out var vt) &&
|
||||
IsValidTargetType(vt, valueType)
|
||||
select CreateInstance<TriValueValidator>(validator.ValidatorType, valueType,
|
||||
it => { it.ApplyOnArrayElement = validator.ApplyOnArrayElement; });
|
||||
}
|
||||
|
||||
public static IEnumerable<TriAttributeValidator> CreateAttributeValidatorsFor(
|
||||
IReadOnlyList<Attribute> attributes)
|
||||
{
|
||||
return
|
||||
from attribute in attributes
|
||||
from validator in AllAttributeValidatorTypes
|
||||
where IsAttributeValidatorType(validator.ValidatorType, out var vt) &&
|
||||
IsValidTargetType(vt, attribute.GetType())
|
||||
select CreateInstance<TriAttributeValidator>(validator.ValidatorType, attribute.GetType(), it =>
|
||||
{
|
||||
it.ApplyOnArrayElement = validator.ApplyOnArrayElement;
|
||||
it.RawAttribute = attribute;
|
||||
});
|
||||
}
|
||||
|
||||
public static IEnumerable<TriPropertyHideProcessor> CreateHideProcessorsFor(IReadOnlyList<Attribute> attributes)
|
||||
{
|
||||
return
|
||||
|
34
README.md
34
README.md
@ -18,6 +18,9 @@ public class BasicSample : TriMonoBehaviour
|
||||
[PropertyTooltip("My Tooltip")]
|
||||
public float unityField;
|
||||
|
||||
[Required]
|
||||
public Material mat;
|
||||
|
||||
[InlineEditor]
|
||||
public SampleScriptableObject objectReference;
|
||||
|
||||
@ -133,6 +136,37 @@ public class TriBoxGroupDrawer : TriGroupDrawer<DeclareBoxGroupAttribute>
|
||||
}
|
||||
```
|
||||
|
||||
Custom Value Validator
|
||||
```csharp
|
||||
using TriInspector;
|
||||
|
||||
[assembly: RegisterTriValueValidator(typeof(MissingReferenceValidator<>))]
|
||||
|
||||
public class MissingReferenceValidator<T> : TriValueValidator<T>
|
||||
where T : UnityEngine.Object
|
||||
{
|
||||
public override TriValidationResult Validate(TriValue<T> propertyValue)
|
||||
{
|
||||
// ...
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Custom Attribute Validator
|
||||
```csharp
|
||||
using TriInspector;
|
||||
|
||||
[assembly: RegisterTriAttributeValidator(typeof(RequiredValidator), ApplyOnArrayElement = true)]
|
||||
|
||||
public class RequiredValidator : TriAttributeValidator<RequiredAttribute>
|
||||
{
|
||||
public override TriValidationResult Validate(TriProperty property)
|
||||
{
|
||||
// ...
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Property Hide Processor
|
||||
```csharp
|
||||
using TriInspector;
|
||||
|
10
Runtime/Attributes/RequiredAttribute.cs
Normal file
10
Runtime/Attributes/RequiredAttribute.cs
Normal file
@ -0,0 +1,10 @@
|
||||
using System;
|
||||
|
||||
namespace TriInspector
|
||||
{
|
||||
[AttributeUsage((AttributeTargets.Field | AttributeTargets.Property))]
|
||||
public class RequiredAttribute : Attribute
|
||||
{
|
||||
public string Message { get; set; }
|
||||
}
|
||||
}
|
3
Runtime/Attributes/RequiredAttribute.cs.meta
Normal file
3
Runtime/Attributes/RequiredAttribute.cs.meta
Normal file
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d886b45ac8b1474db5cdfd07c859e616
|
||||
timeCreated: 1642261718
|
Loading…
Reference in New Issue
Block a user