2026-08-26 11:24:13 +00:00
|
|
|
using System.Collections.Generic;
|
|
|
|
|
using System.Globalization;
|
|
|
|
|
using System.Linq;
|
|
|
|
|
|
|
|
|
|
namespace Sozsoft.Platform.Editors;
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Tek bir Script Builder kurali. <see cref="EditorScript"/> uzerindeki tarif
|
|
|
|
|
/// metotlariyla olusturulur, akici metotlarla kosul ve tetikleyici eklenir.
|
|
|
|
|
/// </summary>
|
|
|
|
|
public sealed class EditorScriptRule
|
|
|
|
|
{
|
|
|
|
|
/// <summary>Zorunlu parametreler. Dialogdaki isRuleComplete kontrolunun karsiligi.</summary>
|
|
|
|
|
private static readonly Dictionary<string, string[]> RequiredParams = new Dictionary<string, string[]>
|
|
|
|
|
{
|
|
|
|
|
["multiply"] = new[] { "left", "right", "target" },
|
|
|
|
|
["subtract"] = new[] { "left", "right", "target" },
|
|
|
|
|
["percent"] = new[] { "amount", "rate", "target" },
|
|
|
|
|
["sum"] = new[] { "target" },
|
|
|
|
|
["formula"] = new[] { "target", "expression" },
|
|
|
|
|
["today"] = new[] { "target" },
|
|
|
|
|
["days"] = new[] { "start", "end", "target" },
|
|
|
|
|
["hours"] = new[] { "start", "end", "target" },
|
|
|
|
|
["copy"] = new[] { "source", "target" },
|
|
|
|
|
["setValue"] = new[] { "target", "text" },
|
|
|
|
|
["clear"] = new string[0],
|
|
|
|
|
["readOnly"] = new[] { "target" },
|
|
|
|
|
["notify"] = new[] { "message" },
|
|
|
|
|
["ask"] = new[] { "message" },
|
|
|
|
|
["openUrl"] = new[] { "url" },
|
|
|
|
|
["apiToField"] = new[] { "target", "url" },
|
2026-09-21 15:01:40 +00:00
|
|
|
["location"] = new[] { "lat", "lng" },
|
2026-08-26 11:24:13 +00:00
|
|
|
["custom"] = new[] { "code" }
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/// <summary>Alan listesi isteyen tarifler.</summary>
|
|
|
|
|
private static readonly HashSet<string> FieldListRecipes = new HashSet<string> { "sum", "clear" };
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Grup bazli varsayilan tetikleyici. Deger ureten kurallar form acilirken de
|
|
|
|
|
/// calisir; kullaniciya bir sey gosteren ya da dis cagri yapan kurallar
|
|
|
|
|
/// yalnizca deger degistiginde calisir.
|
|
|
|
|
/// </summary>
|
|
|
|
|
private static readonly Dictionary<string, EditorScriptTrigger> GroupDefaultTrigger =
|
|
|
|
|
new Dictionary<string, EditorScriptTrigger>
|
|
|
|
|
{
|
|
|
|
|
["multiply"] = EditorScriptTrigger.Both,
|
|
|
|
|
["subtract"] = EditorScriptTrigger.Both,
|
|
|
|
|
["percent"] = EditorScriptTrigger.Both,
|
|
|
|
|
["sum"] = EditorScriptTrigger.Both,
|
|
|
|
|
["formula"] = EditorScriptTrigger.Both,
|
|
|
|
|
["today"] = EditorScriptTrigger.Both,
|
|
|
|
|
["days"] = EditorScriptTrigger.Both,
|
|
|
|
|
["hours"] = EditorScriptTrigger.Both,
|
|
|
|
|
["copy"] = EditorScriptTrigger.Both,
|
|
|
|
|
["setValue"] = EditorScriptTrigger.Both,
|
|
|
|
|
["clear"] = EditorScriptTrigger.Both,
|
|
|
|
|
["readOnly"] = EditorScriptTrigger.Both,
|
|
|
|
|
["notify"] = EditorScriptTrigger.Change,
|
|
|
|
|
["ask"] = EditorScriptTrigger.Change,
|
|
|
|
|
["openUrl"] = EditorScriptTrigger.Change,
|
|
|
|
|
["apiToField"] = EditorScriptTrigger.Change,
|
2026-09-21 15:01:40 +00:00
|
|
|
["location"] = EditorScriptTrigger.Change,
|
2026-08-26 11:24:13 +00:00
|
|
|
["custom"] = EditorScriptTrigger.Change
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
private readonly string _recipe;
|
|
|
|
|
private readonly List<KeyValuePair<string, string>> _params = new List<KeyValuePair<string, string>>();
|
|
|
|
|
private readonly List<string> _fields = new List<string>();
|
|
|
|
|
private readonly List<EditorScriptCondition> _conditions = new List<EditorScriptCondition>();
|
|
|
|
|
|
|
|
|
|
private string _id;
|
|
|
|
|
private bool _anyCondition;
|
|
|
|
|
private EditorScriptTrigger _trigger = EditorScriptTrigger.Default;
|
|
|
|
|
|
|
|
|
|
internal EditorScriptRule(string recipe)
|
|
|
|
|
{
|
|
|
|
|
_recipe = recipe;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* ---------------------------------------------------------------- *
|
|
|
|
|
* Akici yapilandirma
|
|
|
|
|
* ---------------------------------------------------------------- */
|
|
|
|
|
|
|
|
|
|
/// <summary>Kural yalnizca verilen kosullarin <b>hepsi</b> saglandiginda calisir.</summary>
|
|
|
|
|
public EditorScriptRule When(params EditorScriptCondition[] conditions) => AddConditions(conditions, false);
|
|
|
|
|
|
|
|
|
|
/// <summary>Kural verilen kosullardan <b>herhangi biri</b> saglandiginda calisir.</summary>
|
|
|
|
|
public EditorScriptRule WhenAny(params EditorScriptCondition[] conditions) => AddConditions(conditions, true);
|
|
|
|
|
|
|
|
|
|
/// <summary>Yalnizca alanin degeri degistiginde calissin.</summary>
|
|
|
|
|
public EditorScriptRule OnChange() => WithTrigger(EditorScriptTrigger.Change);
|
|
|
|
|
|
|
|
|
|
/// <summary>Yalnizca form acilirken calissin (varsayilan deger uretmek icin).</summary>
|
|
|
|
|
public EditorScriptRule OnOpen() => WithTrigger(EditorScriptTrigger.Open);
|
|
|
|
|
|
|
|
|
|
/// <summary>Hem acilista hem deger degistiginde calissin.</summary>
|
|
|
|
|
public EditorScriptRule OnOpenAndChange() => WithTrigger(EditorScriptTrigger.Both);
|
|
|
|
|
|
|
|
|
|
/// <summary>Tetikleyiciyi acikca belirler. Default ise tarifin grup varsayilani kullanilir.</summary>
|
|
|
|
|
public EditorScriptRule WithTrigger(EditorScriptTrigger trigger)
|
|
|
|
|
{
|
|
|
|
|
_trigger = trigger;
|
|
|
|
|
return this;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Kural kimligini sabitler. Verilmezse Build sirasinda tarif adi ve sira
|
|
|
|
|
/// numarasindan uretilir (ornek: "multiply_1").
|
|
|
|
|
/// </summary>
|
|
|
|
|
public EditorScriptRule WithId(string id)
|
|
|
|
|
{
|
|
|
|
|
_id = id;
|
|
|
|
|
return this;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>Tek kurallik script metni. Birden fazla kural icin <see cref="EditorScript.Build(EditorScriptRule[])"/> kullanin.</summary>
|
|
|
|
|
public string ToScript() => EditorScript.Build(this);
|
|
|
|
|
|
|
|
|
|
/// <summary>Kurali dogrudan EditorScript alanina atanabilir hale getirir.</summary>
|
|
|
|
|
public static implicit operator string(EditorScriptRule rule) => rule?.ToScript() ?? string.Empty;
|
|
|
|
|
|
|
|
|
|
/* ---------------------------------------------------------------- *
|
|
|
|
|
* Ic kullanim
|
|
|
|
|
* ---------------------------------------------------------------- */
|
|
|
|
|
|
|
|
|
|
internal EditorScriptRule Set(string key, string value)
|
|
|
|
|
{
|
|
|
|
|
// Bos parametreler dialogda da yazilmaz; JSON'un birebir esit kalmasi icin atlanir.
|
|
|
|
|
if (!string.IsNullOrEmpty(value))
|
|
|
|
|
{
|
|
|
|
|
_params.Add(new KeyValuePair<string, string>(key, value));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return this;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
internal EditorScriptRule SetNumber(string key, int? value)
|
|
|
|
|
=> value.HasValue ? Set(key, value.Value.ToString(CultureInfo.InvariantCulture)) : this;
|
|
|
|
|
|
|
|
|
|
internal EditorScriptRule SetFields(IEnumerable<string> fields)
|
|
|
|
|
{
|
|
|
|
|
if (fields != null)
|
|
|
|
|
{
|
|
|
|
|
_fields.AddRange(fields.Where(field => !string.IsNullOrEmpty(field)));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return this;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
internal void EnsureId(int order)
|
|
|
|
|
{
|
|
|
|
|
if (string.IsNullOrEmpty(_id))
|
|
|
|
|
{
|
|
|
|
|
_id = _recipe + "_" + order.ToString(CultureInfo.InvariantCulture);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
internal EditorScriptTrigger EffectiveTrigger()
|
|
|
|
|
{
|
|
|
|
|
if (_trigger != EditorScriptTrigger.Default)
|
|
|
|
|
{
|
|
|
|
|
return _trigger;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return GroupDefaultTrigger.TryGetValue(_recipe, out var trigger) ? trigger : EditorScriptTrigger.Change;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
internal bool IsComplete()
|
|
|
|
|
{
|
|
|
|
|
if (!RequiredParams.TryGetValue(_recipe, out var required))
|
|
|
|
|
{
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (_conditions.Any(condition => !condition.IsComplete()))
|
|
|
|
|
{
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (FieldListRecipes.Contains(_recipe) && _fields.Count == 0)
|
|
|
|
|
{
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return required.All(key => _params.Any(pair => pair.Key == key && pair.Value.Trim().Length > 0));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
internal string ToJson()
|
|
|
|
|
{
|
|
|
|
|
var conditions = EffectiveConditions();
|
|
|
|
|
|
|
|
|
|
var json = "{\"id\":" + EditorScript.Quote(_id) +
|
|
|
|
|
",\"recipe\":" + EditorScript.Quote(_recipe) +
|
|
|
|
|
",\"condition\":" + conditions[0].ToJson();
|
|
|
|
|
|
|
|
|
|
if (conditions.Count > 1)
|
|
|
|
|
{
|
|
|
|
|
json += ",\"conditions\":[" + string.Join(",", conditions.Skip(1).Select(c => c.ToJson())) + "]" +
|
|
|
|
|
",\"join\":" + EditorScript.Quote(_anyCondition ? "or" : "and");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
json += ",\"params\":{" +
|
|
|
|
|
string.Join(",", _params.Select(pair => EditorScript.Quote(pair.Key) + ":" + EditorScript.Quote(pair.Value))) +
|
|
|
|
|
"}";
|
|
|
|
|
|
|
|
|
|
json += ",\"fields\":[" + string.Join(",", _fields.Select(EditorScript.Quote)) + "]";
|
|
|
|
|
|
|
|
|
|
if (_trigger != EditorScriptTrigger.Default)
|
|
|
|
|
{
|
|
|
|
|
json += ",\"trigger\":" + EditorScript.Quote(TriggerName(_trigger));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return json + "}";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Kuralin tek satirlik kodu. runsOnOpen yalnizca script'in tamami acilista da
|
|
|
|
|
/// calisiyorsa true gelir; o durumda her kuralin ne zaman calisacagi isReady
|
|
|
|
|
/// ile ayristirilir.
|
|
|
|
|
/// </summary>
|
|
|
|
|
internal string ToCode(bool runsOnOpen)
|
|
|
|
|
{
|
|
|
|
|
var body = BuildBody();
|
|
|
|
|
if (string.IsNullOrEmpty(body))
|
|
|
|
|
{
|
|
|
|
|
return string.Empty;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var guards = new List<string>();
|
|
|
|
|
|
|
|
|
|
if (runsOnOpen)
|
|
|
|
|
{
|
|
|
|
|
var trigger = EffectiveTrigger();
|
|
|
|
|
if (trigger == EditorScriptTrigger.Change)
|
|
|
|
|
{
|
|
|
|
|
guards.Add("!isReady");
|
|
|
|
|
}
|
|
|
|
|
else if (trigger == EditorScriptTrigger.Open)
|
|
|
|
|
{
|
|
|
|
|
guards.Add("isReady");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// readOnly'de kosul dogrudan aksiyonun argumanidir, if ile sarmalanmaz.
|
|
|
|
|
if (_recipe != "readOnly")
|
|
|
|
|
{
|
|
|
|
|
var condition = ConditionExpression();
|
|
|
|
|
if (!string.IsNullOrEmpty(condition))
|
|
|
|
|
{
|
|
|
|
|
guards.Add(condition);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return guards.Count > 0 ? "if (" + string.Join(" && ", guards) + ") " + body : body;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private List<EditorScriptCondition> EffectiveConditions()
|
|
|
|
|
=> _conditions.Count > 0 ? _conditions : new List<EditorScriptCondition> { EditorScriptCondition.Always() };
|
|
|
|
|
|
|
|
|
|
private string ConditionExpression()
|
|
|
|
|
{
|
|
|
|
|
var parts = EffectiveConditions()
|
|
|
|
|
.Select(condition => condition.ToExpression())
|
|
|
|
|
.Where(expression => !string.IsNullOrEmpty(expression))
|
|
|
|
|
.ToList();
|
|
|
|
|
|
|
|
|
|
if (parts.Count == 0)
|
|
|
|
|
{
|
|
|
|
|
return string.Empty;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (parts.Count == 1)
|
|
|
|
|
{
|
|
|
|
|
return parts[0];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return string.Join(_anyCondition ? " || " : " && ", parts.Select(part => "(" + part + ")"));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private EditorScriptRule AddConditions(EditorScriptCondition[] conditions, bool any)
|
|
|
|
|
{
|
|
|
|
|
if (conditions != null)
|
|
|
|
|
{
|
|
|
|
|
_conditions.AddRange(conditions.Where(condition => condition != null));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (_conditions.Count > 1)
|
|
|
|
|
{
|
|
|
|
|
_anyCondition = any;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return this;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private string Param(string key)
|
|
|
|
|
{
|
|
|
|
|
var match = _params.FirstOrDefault(pair => pair.Key == key);
|
|
|
|
|
return match.Key == null ? string.Empty : match.Value;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private string Digits() => Param("digits").Length > 0 ? Param("digits") : "2";
|
|
|
|
|
|
|
|
|
|
private string BuildBody()
|
|
|
|
|
{
|
|
|
|
|
var q = new System.Func<string, string>(EditorScript.Quote);
|
|
|
|
|
var n = new System.Func<string, string>(EditorScript.NumberOrField);
|
|
|
|
|
|
|
|
|
|
switch (_recipe)
|
|
|
|
|
{
|
|
|
|
|
case "multiply":
|
|
|
|
|
return "set(" + q(Param("target")) + ", round(" + n(Param("left")) + " * " + n(Param("right")) + ", " + Digits() + "))";
|
|
|
|
|
|
|
|
|
|
case "subtract":
|
|
|
|
|
return "set(" + q(Param("target")) + ", round(" + n(Param("left")) + " - " + n(Param("right")) + ", " + Digits() + "))";
|
|
|
|
|
|
|
|
|
|
case "percent":
|
|
|
|
|
{
|
|
|
|
|
var amount = n(Param("amount"));
|
|
|
|
|
var portion = amount + " * " + n(Param("rate")) + " / 100";
|
|
|
|
|
var expression = Param("mode") == "add"
|
|
|
|
|
? amount + " + " + portion
|
|
|
|
|
: Param("mode") == "subtract"
|
|
|
|
|
? amount + " - " + portion
|
|
|
|
|
: portion;
|
|
|
|
|
return "set(" + q(Param("target")) + ", round(" + expression + ", " + Digits() + "))";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
case "sum":
|
|
|
|
|
return "set(" + q(Param("target")) + ", round(sum(" + string.Join(", ", _fields.Select(q)) + "), " + Digits() + "))";
|
|
|
|
|
|
|
|
|
|
case "formula":
|
|
|
|
|
return "set(" + q(Param("target")) + ", " + (Param("expression").Length > 0 ? Param("expression") : "null") + ")";
|
|
|
|
|
|
|
|
|
|
case "today":
|
|
|
|
|
{
|
|
|
|
|
var offset = Param("offset");
|
|
|
|
|
var parsed = int.TryParse(offset, NumberStyles.Integer, CultureInfo.InvariantCulture, out var value) ? value : 0;
|
|
|
|
|
var start = parsed != 0
|
|
|
|
|
? "new Date(Date.now() + " + parsed.ToString(CultureInfo.InvariantCulture) + " * 86400000)"
|
|
|
|
|
: "new Date()";
|
|
|
|
|
return "set(" + q(Param("target")) + ", " + start + ".toISOString().slice(0, 10))";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
case "days":
|
|
|
|
|
return "set(" + q(Param("target")) + ", days(" + q(Param("start")) + ", " + q(Param("end")) + "))";
|
|
|
|
|
|
|
|
|
|
case "hours":
|
|
|
|
|
return "set(" + q(Param("target")) + ", hours(" + q(Param("start")) + ", " + q(Param("end")) + "))";
|
|
|
|
|
|
|
|
|
|
case "copy":
|
|
|
|
|
return "copy(" + q(Param("source")) + ", " + q(Param("target")) + ")";
|
|
|
|
|
|
|
|
|
|
case "setValue":
|
|
|
|
|
return "set(" + q(Param("target")) + ", tpl(" + q(Param("text")) + "))";
|
|
|
|
|
|
|
|
|
|
case "clear":
|
|
|
|
|
return "clear(" + string.Join(", ", _fields.Select(q)) + ")";
|
|
|
|
|
|
|
|
|
|
case "readOnly":
|
|
|
|
|
{
|
|
|
|
|
var expression = ConditionExpression();
|
|
|
|
|
if (expression.Length == 0)
|
|
|
|
|
{
|
|
|
|
|
expression = "true";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var flag = Param("invert") == "true" ? "!(" + expression + ")" : expression;
|
|
|
|
|
return "readOnly(" + q(Param("target")) + ", " + flag + ")";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
case "notify":
|
|
|
|
|
return "notify(" + q(Param("message")) + ")";
|
|
|
|
|
|
|
|
|
|
case "ask":
|
|
|
|
|
return "if (!ask(" + q(Param("message")) + ")) return";
|
|
|
|
|
|
|
|
|
|
case "openUrl":
|
|
|
|
|
return Param("target").Length > 0 && Param("target") != "_blank"
|
|
|
|
|
? "openUrl(" + q(Param("url")) + ", " + q(Param("target")) + ")"
|
|
|
|
|
: "openUrl(" + q(Param("url")) + ")";
|
|
|
|
|
|
|
|
|
|
case "apiToField":
|
|
|
|
|
return "set(" + q(Param("target")) + ", await api(" + q(Param("url")) +
|
|
|
|
|
(Param("path").Length > 0 ? ", " + q(Param("path")) : string.Empty) + "))";
|
|
|
|
|
|
2026-09-21 15:01:40 +00:00
|
|
|
case "location":
|
|
|
|
|
return "await geoTo(" + q(Param("lat")) + ", " + q(Param("lng")) +
|
|
|
|
|
(Param("accuracy").Length > 0 ? ", " + q(Param("accuracy")) : string.Empty) + ")";
|
|
|
|
|
|
2026-08-26 11:24:13 +00:00
|
|
|
case "custom":
|
|
|
|
|
return Param("code").Trim().TrimEnd(';');
|
|
|
|
|
|
|
|
|
|
default:
|
|
|
|
|
return string.Empty;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static string TriggerName(EditorScriptTrigger trigger)
|
|
|
|
|
{
|
|
|
|
|
switch (trigger)
|
|
|
|
|
{
|
|
|
|
|
case EditorScriptTrigger.Change: return "change";
|
|
|
|
|
case EditorScriptTrigger.Open: return "open";
|
|
|
|
|
default: return "both";
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|