Custom Component düzenlemesi

This commit is contained in:
Sedat Öztürk 2026-08-05 23:51:43 +03:00
parent 9cc77fb3a1
commit 25ae424f42
40 changed files with 6852 additions and 4592 deletions

View file

@ -22,6 +22,19 @@ public class CustomComponentAppService : CrudAppService<
{
}
public override async Task<CustomComponentDto> UpdateAsync(
Guid id,
CreateUpdateCustomComponentDto input)
{
if (input.Props is null)
{
var existingComponent = await Repository.GetAsync(id);
input.Props = existingComponent.Props;
}
return await base.UpdateAsync(id, input);
}
public async Task<List<CustomComponentDto>> GetActiveComponentsAsync()
{
var components = await Repository.GetListAsync(x => x.IsActive);

View file

@ -25,7 +25,7 @@ public static class PlatformConsts
{
public static string Disabled = "{ \"disabled\" : true }";
public static string ShowClearButton = "{ \"showClearButton\" : true }";
public static string HtmlEditorOptions = """{"mediaResizing":{"enabled":true},"imageUpload":{"tabs":["file","url"],"fileUploadMode":"base64"},"toolbar":{"multiline":true,"items":[{"name":"undo"},{"name":"redo"},{"name":"separator"},{"name":"size","acceptedValues":["8pt","10pt","12pt","14pt","18pt","24pt","36pt"],"options":{"inputAttr":{"aria-label":"Font size"}}},{"name":"font","acceptedValues":["Arial","Courier New","Georgia","Impact","Lucida Console","Tahoma","Times New Roman","Verdana"],"options":{"inputAttr":{"aria-label":"Font family"}}},{"name":"separator"},{"name":"bold"},{"name":"italic"},{"name":"strike"},{"name":"underline"},{"name":"separator"},{"name":"alignLeft"},{"name":"alignCenter"},{"name":"alignRight"},{"name":"alignJustify"},{"name":"separator"},{"name":"orderedList"},{"name":"bulletList"},{"name":"separator"},{"name":"header","acceptedValues":[false,1,2,3,4,5]},{"name":"separator"},{"name":"color"},{"name":"background"},{"name":"separator"},{"name":"link"},{"name":"image"},{"name":"separator"},{"name":"clear"},{"name":"codeBlock"},{"name":"blockquote"},{"name":"separator"},{"name":"insertTable"},{"name":"deleteTable"},{"name":"insertRowAbove"},{"name":"insertRowBelow"},{"name":"deleteRow"},{"name":"insertColumnLeft"},{"name":"insertColumnRight"},{"name":"deleteColumn"},{"name":"cellProperties"},{"name":"tableProperties"}]},"encodeHtml":true}""";
public static string HtmlEditorOptions = """{"mediaResizing":{"enabled":true},"imageUpload":{"tabs":["file","url"],"fileUploadMode":"base64"},"toolbar":{"multiline":true,"items":[{"name":"undo"},{"name":"redo"},{"name":"separator"},{"name":"size","acceptedValues":["8pt","10pt","12pt","14pt","18pt","24pt","36pt"],"options":{"inputAttr":{"aria-label":"Font size"}}},{"name":"font","acceptedValues":["Arial","Courier New","Georgia","Impact","Lucida Console","Tahoma","Times New Roman","Verdana"],"options":{"inputAttr":{"aria-label":"Font family"}}},{"name":"separator"},{"name":"bold"},{"name":"italic"},{"name":"strike"},{"name":"underline"},{"name":"separator"},{"name":"alignLeft"},{"name":"alignCenter"},{"name":"alignRight"},{"name":"alignJustify"},{"name":"separator"},{"name":"orderedList"},{"name":"bulletList"},{"name":"separator"},{"name":"header","acceptedValues":[false,1,2,3,4,5]},{"name":"separator"},{"name":"color"},{"name":"background"},{"name":"separator"},{"name":"link"},{"name":"image"},{"name":"separator"},{"name":"clear"},{"name":"codeBlock"},{"name":"blockquote"},{"name":"separator"},{"name":"insertTable"},{"name":"deleteTable"},{"name":"insertRowAbove"},{"name":"insertRowBelow"},{"name":"deleteRow"},{"name":"insertColumnLeft"},{"name":"insertColumnRight"},{"name":"deleteColumn"},{"name":"cellProperties"},{"name":"tableProperties"}]},"encodeHtml":true,"height":200}""";
public static string PhoneEditorOptions = "{\"format\": \"phoneGlobal\", \"mask\":\"(000) 000-0000\", \"maskInvalidMessage\":\"Lütfen geçerli bir telefon numarası girin\", \"useMaskedValue\":false, \"maskRules\": { \"X\": \"[0-9]\" }, \"placeholder\": \"(555) 123-4567\" }";
public static string TimeSpanOptions = "{\"type\":\"time\",\"pickerType\":\"list\",\"displayFormat\":\"HH:mm\",\"dateSerializationFormat\":\"yyyy-MM-ddTHH:mm:ss\",\"interval\":5,\"width\":\"100%\"}";
public static string NumberStandartFormat(int precision = 2) => "{ \"format\": { \"type\": \"fixedPoint\", \"precision\": " + precision + " }, \"useMaskBehavior\": true, \"showSpinButtons\": true }";

View file

@ -196,8 +196,8 @@ public class PlatformDbContext :
b.Property(a => a.Name).IsRequired().HasMaxLength(128);
b.Property(a => a.ApiUrl).HasMaxLength(256);
b.Property(a => a.Description).HasColumnType("text");
b.Property(a => a.Tenants).HasColumnType("text");
b.Property(a => a.Description).HasColumnType("ntext");
b.Property(a => a.Tenants).HasColumnType("ntext");
b.HasIndex(x => new { x.Name }).IsUnique().HasFilter(null);
});
@ -349,60 +349,60 @@ public class PlatformDbContext :
b.Property(a => a.ListFormType).HasMaxLength(20);
// Uzun JSON alanları için nvarchar(max)
b.Property(a => a.SelectCommand).HasColumnType("text");
b.Property(a => a.SelectFieldsDefaultValueJson).HasColumnType("text");
b.Property(a => a.SelectCommand).HasColumnType("ntext");
b.Property(a => a.SelectFieldsDefaultValueJson).HasColumnType("ntext");
b.Property(a => a.DefaultFilter).IsUnicode(true);
b.Property(a => a.ColumnOptionJson).HasColumnType("text");
b.Property(a => a.PivotOptionJson).HasColumnType("text");
b.Property(a => a.FilterRowJson).HasColumnType("text");
b.Property(a => a.HeaderFilterJson).HasColumnType("text");
b.Property(a => a.FilterPanelJson).HasColumnType("text");
b.Property(a => a.SearchPanelJson).HasColumnType("text");
b.Property(a => a.GroupPanelJson).HasColumnType("text");
b.Property(a => a.SelectionJson).HasColumnType("text");
b.Property(a => a.PagerOptionJson).HasColumnType("text");
b.Property(a => a.EditingOptionJson).HasColumnType("text");
b.Property(a => a.EditingFormJson).HasColumnType("text");
b.Property(a => a.PermissionJson).HasColumnType("text");
b.Property(a => a.InsertFieldsDefaultValueJson).HasColumnType("text");
b.Property(a => a.UpdateFieldsDefaultValueJson).HasColumnType("text");
b.Property(a => a.DeleteFieldsDefaultValueJson).HasColumnType("text");
b.Property(a => a.CommandColumnJson).HasColumnType("text");
b.Property(a => a.StateStoringJson).HasColumnType("text");
b.Property(a => a.DeleteCommand).HasColumnType("text");
b.Property(a => a.UpdateCommand).HasColumnType("text");
b.Property(a => a.InsertCommand).HasColumnType("text");
b.Property(a => a.CustomJsSourcesJson).HasColumnType("text");
b.Property(a => a.CustomStyleSourcesJson).HasColumnType("text");
b.Property(a => a.FormFieldsDefaultValueJson).HasColumnType("text");
b.Property(a => a.SubFormsJson).HasColumnType("text");
b.Property(a => a.WidgetsJson).HasColumnType("text");
b.Property(a => a.WorkflowJson).HasColumnType("text");
b.Property(a => a.ExtraFilterJson).HasColumnType("text");
b.Property(a => a.LayoutJson).HasColumnType("text");
b.Property(a => a.TodoOptionJson).HasColumnType("text");
b.Property(a => a.CommonJson).HasColumnType("text");
b.Property(a => a.DataSourceJson).HasColumnType("text");
b.Property(a => a.AdaptiveLayoutJson).HasColumnType("text");
b.Property(a => a.AnimationJson).HasColumnType("text");
b.Property(a => a.AnnotationsJson).HasColumnType("text");
b.Property(a => a.ArgumentAxisJson).HasColumnType("text");
b.Property(a => a.CommonAnnotationsSettingsJson).HasColumnType("text");
b.Property(a => a.CommonAxisSettingsJson).HasColumnType("text");
b.Property(a => a.CommonPaneSettingsJson).HasColumnType("text");
b.Property(a => a.CommonSeriesSettingsJson).HasColumnType("text");
b.Property(a => a.CrosshairJson).HasColumnType("text");
b.Property(a => a.ExportJson).HasColumnType("text");
b.Property(a => a.LegendJson).HasColumnType("text");
b.Property(a => a.MarginJson).HasColumnType("text");
b.Property(a => a.PanesJson).HasColumnType("text");
b.Property(a => a.ScrollBarJson).HasColumnType("text");
b.Property(a => a.SeriesJson).HasColumnType("text");
b.Property(a => a.SizeJson).HasColumnType("text");
b.Property(a => a.TitleJson).HasColumnType("text");
b.Property(a => a.TooltipJson).HasColumnType("text");
b.Property(a => a.ValueAxisJson).HasColumnType("text");
b.Property(a => a.ZoomAndPanJson).HasColumnType("text");
b.Property(a => a.ColumnOptionJson).HasColumnType("ntext");
b.Property(a => a.PivotOptionJson).HasColumnType("ntext");
b.Property(a => a.FilterRowJson).HasColumnType("ntext");
b.Property(a => a.HeaderFilterJson).HasColumnType("ntext");
b.Property(a => a.FilterPanelJson).HasColumnType("ntext");
b.Property(a => a.SearchPanelJson).HasColumnType("ntext");
b.Property(a => a.GroupPanelJson).HasColumnType("ntext");
b.Property(a => a.SelectionJson).HasColumnType("ntext");
b.Property(a => a.PagerOptionJson).HasColumnType("ntext");
b.Property(a => a.EditingOptionJson).HasColumnType("ntext");
b.Property(a => a.EditingFormJson).HasColumnType("ntext");
b.Property(a => a.PermissionJson).HasColumnType("ntext");
b.Property(a => a.InsertFieldsDefaultValueJson).HasColumnType("ntext");
b.Property(a => a.UpdateFieldsDefaultValueJson).HasColumnType("ntext");
b.Property(a => a.DeleteFieldsDefaultValueJson).HasColumnType("ntext");
b.Property(a => a.CommandColumnJson).HasColumnType("ntext");
b.Property(a => a.StateStoringJson).HasColumnType("ntext");
b.Property(a => a.DeleteCommand).HasColumnType("ntext");
b.Property(a => a.UpdateCommand).HasColumnType("ntext");
b.Property(a => a.InsertCommand).HasColumnType("ntext");
b.Property(a => a.CustomJsSourcesJson).HasColumnType("ntext");
b.Property(a => a.CustomStyleSourcesJson).HasColumnType("ntext");
b.Property(a => a.FormFieldsDefaultValueJson).HasColumnType("ntext");
b.Property(a => a.SubFormsJson).HasColumnType("ntext");
b.Property(a => a.WidgetsJson).HasColumnType("ntext");
b.Property(a => a.WorkflowJson).HasColumnType("ntext");
b.Property(a => a.ExtraFilterJson).HasColumnType("ntext");
b.Property(a => a.LayoutJson).HasColumnType("ntext");
b.Property(a => a.TodoOptionJson).HasColumnType("ntext");
b.Property(a => a.CommonJson).HasColumnType("ntext");
b.Property(a => a.DataSourceJson).HasColumnType("ntext");
b.Property(a => a.AdaptiveLayoutJson).HasColumnType("ntext");
b.Property(a => a.AnimationJson).HasColumnType("ntext");
b.Property(a => a.AnnotationsJson).HasColumnType("ntext");
b.Property(a => a.ArgumentAxisJson).HasColumnType("ntext");
b.Property(a => a.CommonAnnotationsSettingsJson).HasColumnType("ntext");
b.Property(a => a.CommonAxisSettingsJson).HasColumnType("ntext");
b.Property(a => a.CommonPaneSettingsJson).HasColumnType("ntext");
b.Property(a => a.CommonSeriesSettingsJson).HasColumnType("ntext");
b.Property(a => a.CrosshairJson).HasColumnType("ntext");
b.Property(a => a.ExportJson).HasColumnType("ntext");
b.Property(a => a.LegendJson).HasColumnType("ntext");
b.Property(a => a.MarginJson).HasColumnType("ntext");
b.Property(a => a.PanesJson).HasColumnType("ntext");
b.Property(a => a.ScrollBarJson).HasColumnType("ntext");
b.Property(a => a.SeriesJson).HasColumnType("ntext");
b.Property(a => a.SizeJson).HasColumnType("ntext");
b.Property(a => a.TitleJson).HasColumnType("ntext");
b.Property(a => a.TooltipJson).HasColumnType("ntext");
b.Property(a => a.ValueAxisJson).HasColumnType("ntext");
b.Property(a => a.ZoomAndPanJson).HasColumnType("ntext");
b.HasIndex(x => new { x.ListFormCode }).IsUnique().HasFilter(null);
@ -438,27 +438,27 @@ public class PlatformDbContext :
b.Property(a => a.BandName).HasMaxLength(128);
// JSON alanlar
b.Property(a => a.ColumnFilterJson).HasColumnType("text");
b.Property(a => a.ColumnHeaderJson).HasColumnType("text");
b.Property(a => a.GroupingJson).HasColumnType("text");
b.Property(a => a.ColumnCustomizationJson).HasColumnType("text");
b.Property(a => a.TotalSummaryJson).HasColumnType("text");
b.Property(a => a.GroupSummaryJson).HasColumnType("text");
b.Property(a => a.JoinTableJson).HasColumnType("text");
b.Property(a => a.LookupJson).HasColumnType("text");
b.Property(a => a.ValidationRuleJson).HasColumnType("text");
b.Property(a => a.ColumnStylingJson).HasColumnType("text");
b.Property(a => a.PermissionJson).HasColumnType("text");
b.Property(a => a.PivotSettingsJson).HasColumnType("text");
b.Property(a => a.ColumnFilterJson).HasColumnType("ntext");
b.Property(a => a.ColumnHeaderJson).HasColumnType("ntext");
b.Property(a => a.GroupingJson).HasColumnType("ntext");
b.Property(a => a.ColumnCustomizationJson).HasColumnType("ntext");
b.Property(a => a.TotalSummaryJson).HasColumnType("ntext");
b.Property(a => a.GroupSummaryJson).HasColumnType("ntext");
b.Property(a => a.JoinTableJson).HasColumnType("ntext");
b.Property(a => a.LookupJson).HasColumnType("ntext");
b.Property(a => a.ValidationRuleJson).HasColumnType("ntext");
b.Property(a => a.ColumnStylingJson).HasColumnType("ntext");
b.Property(a => a.PermissionJson).HasColumnType("ntext");
b.Property(a => a.PivotSettingsJson).HasColumnType("ntext");
// Stil ve format
b.Property(a => a.ColumnCssClass).HasMaxLength(128);
b.Property(a => a.ColumnCssValue).HasMaxLength(256);
b.Property(a => a.Alignment).HasMaxLength(20).HasDefaultValue("left");
b.Property(a => a.Format).HasMaxLength(128);
b.Property(a => a.EditorOptions).HasColumnType("text");
b.Property(a => a.EditorOptions).HasColumnType("ntext");
b.Property(a => a.EditorType2).HasMaxLength(64);
b.Property(a => a.EditorScript).HasColumnType("text");
b.Property(a => a.EditorScript).HasColumnType("ntext");
b.HasIndex(x => new { x.ListFormCode, x.UserId, x.RoleId, x.CultureName, x.FieldName }).IsUnique().HasFilter(null);
});
@ -472,7 +472,7 @@ public class PlatformDbContext :
b.Property(a => a.UserId).HasMaxLength(256);
b.Property(a => a.RoleId).HasMaxLength(256);
b.Property(a => a.FilterName).HasMaxLength(128);
b.Property(a => a.CustomizationData).HasColumnType("text");
b.Property(a => a.CustomizationData).HasColumnType("ntext");
b.Property(a => a.CustomizationType).IsRequired();
});
@ -496,7 +496,7 @@ public class PlatformDbContext :
b.Property(x => x.ImportId).IsRequired();
b.Property(x => x.BlobName).IsRequired().HasMaxLength(256);
b.Property(x => x.Status).IsRequired().HasMaxLength(64);
b.Property(x => x.ErrorsJson).HasColumnType("text");
b.Property(x => x.ErrorsJson).HasColumnType("ntext");
});
builder.Entity<ListFormWorkflow>(b =>
@ -519,7 +519,7 @@ public class PlatformDbContext :
b.Property(x => x.NextOnReject).IsRequired().HasMaxLength(128);
b.Property(x => x.PositionX).IsRequired();
b.Property(x => x.PositionY).IsRequired();
b.Property(x => x.CompareOutcomesJson).HasColumnType("text");
b.Property(x => x.CompareOutcomesJson).HasColumnType("ntext");
});
builder.Entity<Note>(b =>
@ -546,7 +546,7 @@ public class PlatformDbContext :
b.Property(a => a.DataSourceCode).HasMaxLength(64);
b.Property(a => a.BeforeSp).HasMaxLength(128);
b.Property(a => a.AfterSp).HasMaxLength(128);
b.Property(a => a.Options).HasColumnType("text");
b.Property(a => a.Options).HasColumnType("ntext");
b.HasIndex(x => new { x.Name }).IsUnique().HasFilter("[IsDeleted] = 0");
});
@ -638,8 +638,8 @@ public class PlatformDbContext :
b.Property(a => a.Method).IsRequired().HasMaxLength(10);
b.Property(a => a.DataSourceCode).IsRequired().HasMaxLength(128);
b.Property(a => a.Sql).IsRequired();
b.Property(a => a.ParametersJson).HasColumnType("text");
b.Property(a => a.PermissionsJson).HasColumnType("text");
b.Property(a => a.ParametersJson).HasColumnType("ntext");
b.Property(a => a.PermissionsJson).HasColumnType("ntext");
b.HasIndex(x => new { x.TenantId, x.Name }).IsUnique().HasFilter("[IsDeleted] = 0");
});
@ -652,7 +652,9 @@ public class PlatformDbContext :
b.Property(x => x.Name).IsRequired().HasMaxLength(128);
b.Property(x => x.RoutePath).IsRequired().HasMaxLength(512);
b.Property(x => x.Code).IsRequired();
b.Property(x => x.Props).HasMaxLength(1024);
// The visual designer document contains user-facing text. Keep it Unicode so
// Turkish and other non-ASCII property values survive a database round-trip.
b.Property(x => x.Props).HasColumnType("nvarchar(max)");
b.Property(x => x.Description).HasMaxLength(512);
b.Property(x => x.Dependencies).HasMaxLength(2048);
@ -931,9 +933,9 @@ public class PlatformDbContext :
b.ToTable(TableNameResolver.GetFullTableName(nameof(TableNameEnum.About)), Prefix.DbSchema);
b.ConfigureByConvention();
b.Property(x => x.StatsJson).HasColumnType("text");
b.Property(x => x.DescriptionsJson).HasColumnType("text");
b.Property(x => x.SectionsJson).HasColumnType("text");
b.Property(x => x.StatsJson).HasColumnType("ntext");
b.Property(x => x.DescriptionsJson).HasColumnType("ntext");
b.Property(x => x.SectionsJson).HasColumnType("ntext");
});
builder.Entity<Home>(b =>
@ -951,9 +953,9 @@ public class PlatformDbContext :
b.Property(x => x.CtaTitleKey).HasMaxLength(256);
b.Property(x => x.CtaSubtitleKey).HasMaxLength(256);
b.Property(x => x.CtaButtonLabelKey).HasMaxLength(256);
b.Property(x => x.SlidesJson).HasColumnType("text");
b.Property(x => x.FeaturesJson).HasColumnType("text");
b.Property(x => x.SolutionsJson).HasColumnType("text");
b.Property(x => x.SlidesJson).HasColumnType("ntext");
b.Property(x => x.FeaturesJson).HasColumnType("ntext");
b.Property(x => x.SolutionsJson).HasColumnType("ntext");
});
builder.Entity<Service>(b =>
@ -1143,9 +1145,9 @@ public class PlatformDbContext :
b.Property(x => x.Email).HasMaxLength(128);
b.Property(x => x.Location).HasMaxLength(256);
b.Property(x => x.BankJson).HasColumnType("text");
b.Property(x => x.WorkHoursJson).HasColumnType("text");
b.Property(x => x.MapJson).HasColumnType("text");
b.Property(x => x.BankJson).HasColumnType("ntext");
b.Property(x => x.WorkHoursJson).HasColumnType("ntext");
b.Property(x => x.MapJson).HasColumnType("ntext");
});
builder.Entity<LogEntry>(b =>
@ -1162,10 +1164,10 @@ public class PlatformDbContext :
b.Property(x => x.Name).IsRequired().HasMaxLength(256);
b.Property(x => x.DisplayName).HasMaxLength(512);
b.Property(x => x.Description).HasMaxLength(2048);
b.Property(x => x.Code).IsRequired().HasColumnType("text"); // Uzun C# kod metni için
b.Property(x => x.Code).IsRequired().HasColumnType("ntext"); // Uzun C# kod metni için
b.Property(x => x.IsActive).IsRequired().HasDefaultValue(true);
b.Property(x => x.CompilationStatus).IsRequired().HasConversion<string>().HasMaxLength(20);
b.Property(x => x.LastCompilationError).HasColumnType("text"); // Uzun hata mesajları için
b.Property(x => x.LastCompilationError).HasColumnType("ntext"); // Uzun hata mesajları için
b.Property(x => x.LastSuccessfulCompilation).IsRequired(false);
b.Property(x => x.Version).IsRequired().HasDefaultValue(1);
b.Property(x => x.CodeHash).HasMaxLength(64);
@ -1187,7 +1189,7 @@ public class PlatformDbContext :
b.Property(x => x.PublishDate).IsRequired();
b.Property(x => x.Attachments).HasMaxLength(2048);
b.Property(x => x.ViewCount).HasDefaultValue(0);
b.Property(x => x.ImageUrl).HasColumnType("text");
b.Property(x => x.ImageUrl).HasColumnType("ntext");
b.HasIndex(x => new { x.TenantId, x.Title }).IsUnique().HasFilter("[IsDeleted] = 0");
});
@ -1403,7 +1405,7 @@ public class PlatformDbContext :
b.Property(x => x.Description).HasMaxLength(1024);
b.Property(x => x.Status).HasMaxLength(20);
b.Property(x => x.isPublished).HasDefaultValue(false);
b.Property(x => x.Photos).HasColumnType("text");
b.Property(x => x.Photos).HasColumnType("ntext");
b.HasIndex(x => new { x.TenantId, x.CategoryId, x.TypeId, x.Name }).IsUnique().HasFilter("[IsDeleted] = 0");
@ -1452,7 +1454,7 @@ public class PlatformDbContext :
b.Property(x => x.Title).HasMaxLength(256);
b.Property(x => x.ParticipantKey).IsRequired().HasMaxLength(512);
b.Property(x => x.ParticipantIdsJson).IsRequired().HasColumnType("text");
b.Property(x => x.ParticipantIdsJson).IsRequired().HasColumnType("ntext");
b.Property(x => x.LastMessagePreview).HasMaxLength(512);
b.Property(x => x.MessageCount).HasDefaultValue(0);
@ -1469,9 +1471,9 @@ public class PlatformDbContext :
b.Property(x => x.SenderUserName).IsRequired().HasMaxLength(256);
b.Property(x => x.SenderName).IsRequired().HasMaxLength(256);
b.Property(x => x.RecipientIdsJson).IsRequired().HasColumnType("text");
b.Property(x => x.RecipientIdsJson).IsRequired().HasColumnType("ntext");
b.Property(x => x.Text).HasMaxLength(4096);
b.Property(x => x.AttachmentsJson).IsRequired().HasColumnType("text");
b.Property(x => x.AttachmentsJson).IsRequired().HasColumnType("ntext");
b.Property(x => x.SentAt).IsRequired();
b.HasIndex(x => new { x.TenantId, x.ConversationId, x.SentAt });
@ -1575,7 +1577,7 @@ public class PlatformDbContext :
b.Property(x => x.ResourceName).HasMaxLength(64);
b.Property(x => x.MaxForms).HasDefaultValue(500);
b.Property(x => x.CacheKey).HasMaxLength(512).IsRequired();
b.Property(x => x.JsonData).IsRequired().HasColumnType("text");
b.Property(x => x.JsonData).IsRequired().HasColumnType("ntext");
b.Property(x => x.FormCount).HasDefaultValue(0);
b.Property(x => x.FieldCount).HasDefaultValue(0);
b.Property(x => x.JsonLength).HasDefaultValue(0);
@ -1594,7 +1596,7 @@ public class PlatformDbContext :
b.Property(e => e.Title).HasMaxLength(512).IsRequired();
b.Property(e => e.Status).HasMaxLength(64).IsRequired();
b.Property(e => e.Description).HasColumnType("text");
b.Property(e => e.Description).HasColumnType("ntext");
b.Property(e => e.Tags).HasMaxLength(256);
b.Property(e => e.Assignees).HasMaxLength(512);
b.Property(e => e.Priority).HasMaxLength(64);
@ -1610,10 +1612,10 @@ public class PlatformDbContext :
b.Property(e => e.UserName).HasMaxLength(256).IsRequired();
b.Property(e => e.Text).HasMaxLength(512).IsRequired();
b.Property(e => e.Description).HasColumnType("text");
b.Property(e => e.Description).HasColumnType("ntext");
b.Property(e => e.AllDay).HasDefaultValue(false);
b.Property(e => e.RecurrenceRule).HasMaxLength(512);
b.Property(e => e.RecurrenceException).HasColumnType("text");
b.Property(e => e.RecurrenceException).HasColumnType("ntext");
});
ConfigureProviderSpecificModel(builder);

View file

@ -13,7 +13,7 @@ using Volo.Abp.EntityFrameworkCore;
namespace Sozsoft.Platform.Migrations
{
[DbContext(typeof(PlatformDbContext))]
[Migration("20260731143824_Initial")]
[Migration("20260805202101_Initial")]
partial class Initial
{
/// <inheritdoc />
@ -652,7 +652,7 @@ namespace Sozsoft.Platform.Migrations
.HasColumnName("DeletionTime");
b.Property<string>("DescriptionsJson")
.HasColumnType("text");
.HasColumnType("ntext");
b.Property<bool>("IsDeleted")
.ValueGeneratedOnAdd()
@ -669,10 +669,10 @@ namespace Sozsoft.Platform.Migrations
.HasColumnName("LastModifierId");
b.Property<string>("SectionsJson")
.HasColumnType("text");
.HasColumnType("ntext");
b.Property<string>("StatsJson")
.HasColumnType("text");
.HasColumnType("ntext");
b.HasKey("Id");
@ -706,7 +706,7 @@ namespace Sozsoft.Platform.Migrations
.HasColumnName("DeletionTime");
b.Property<string>("Description")
.HasColumnType("text");
.HasColumnType("ntext");
b.Property<DateTime>("EndDate")
.HasColumnType("datetime2");
@ -726,7 +726,7 @@ namespace Sozsoft.Platform.Migrations
.HasColumnName("LastModifierId");
b.Property<string>("RecurrenceException")
.HasColumnType("text");
.HasColumnType("ntext");
b.Property<string>("RecurrenceRule")
.HasMaxLength(512)
@ -764,7 +764,7 @@ namespace Sozsoft.Platform.Migrations
.HasColumnType("nvarchar(256)");
b.Property<string>("Description")
.HasColumnType("text");
.HasColumnType("ntext");
b.Property<bool>("IsActive")
.HasColumnType("bit");
@ -775,7 +775,7 @@ namespace Sozsoft.Platform.Migrations
.HasColumnType("nvarchar(128)");
b.Property<string>("Tenants")
.HasColumnType("text");
.HasColumnType("ntext");
b.HasKey("Id");
@ -829,7 +829,7 @@ namespace Sozsoft.Platform.Migrations
.HasColumnType("datetime2");
b.Property<string>("ImageUrl")
.HasColumnType("text");
.HasColumnType("ntext");
b.Property<bool>("IsDeleted")
.ValueGeneratedOnAdd()
@ -1059,7 +1059,7 @@ namespace Sozsoft.Platform.Migrations
.HasColumnType("nvarchar(128)");
b.Property<string>("Options")
.HasColumnType("text");
.HasColumnType("ntext");
b.Property<int>("WorkerType")
.HasColumnType("int");
@ -1453,7 +1453,7 @@ namespace Sozsoft.Platform.Migrations
.HasColumnType("nvarchar(512)");
b.Property<string>("BankJson")
.HasColumnType("text");
.HasColumnType("ntext");
b.Property<DateTime>("CreationTime")
.HasColumnType("datetime2")
@ -1494,7 +1494,7 @@ namespace Sozsoft.Platform.Migrations
.HasColumnType("nvarchar(256)");
b.Property<string>("MapJson")
.HasColumnType("text");
.HasColumnType("ntext");
b.Property<string>("PhoneNumber")
.HasMaxLength(20)
@ -1504,7 +1504,7 @@ namespace Sozsoft.Platform.Migrations
.HasColumnType("bigint");
b.Property<string>("WorkHoursJson")
.HasColumnType("text");
.HasColumnType("ntext");
b.HasKey("Id");
@ -1898,8 +1898,7 @@ namespace Sozsoft.Platform.Migrations
.HasColumnType("nvarchar(128)");
b.Property<string>("Props")
.HasMaxLength(1024)
.HasColumnType("nvarchar(1024)");
.HasColumnType("nvarchar(max)");
b.Property<string>("RoutePath")
.IsRequired()
@ -1978,10 +1977,10 @@ namespace Sozsoft.Platform.Migrations
.HasColumnType("nvarchar(128)");
b.Property<string>("ParametersJson")
.HasColumnType("text");
.HasColumnType("ntext");
b.Property<string>("PermissionsJson")
.HasColumnType("text");
.HasColumnType("ntext");
b.Property<string>("Sql")
.IsRequired()
@ -2103,7 +2102,7 @@ namespace Sozsoft.Platform.Migrations
b.Property<string>("JsonData")
.IsRequired()
.HasColumnType("text");
.HasColumnType("ntext");
b.Property<int>("JsonLength")
.ValueGeneratedOnAdd()
@ -2323,7 +2322,7 @@ namespace Sozsoft.Platform.Migrations
.HasColumnName("DeletionTime");
b.Property<string>("Description")
.HasColumnType("text");
.HasColumnType("ntext");
b.Property<DateTime?>("DueDate")
.HasColumnType("datetime2");
@ -2458,7 +2457,7 @@ namespace Sozsoft.Platform.Migrations
b.Property<string>("Code")
.IsRequired()
.HasColumnType("text");
.HasColumnType("ntext");
b.Property<string>("CodeHash")
.HasMaxLength(64)
@ -2509,7 +2508,7 @@ namespace Sozsoft.Platform.Migrations
.HasColumnName("IsDeleted");
b.Property<string>("LastCompilationError")
.HasColumnType("text");
.HasColumnType("ntext");
b.Property<DateTime?>("LastModificationTime")
.HasColumnType("datetime2")
@ -2606,7 +2605,7 @@ namespace Sozsoft.Platform.Migrations
.HasColumnType("int");
b.Property<string>("Photos")
.HasColumnType("text");
.HasColumnType("ntext");
b.Property<string>("Place")
.HasMaxLength(256)
@ -2937,7 +2936,7 @@ namespace Sozsoft.Platform.Migrations
.HasColumnName("DeletionTime");
b.Property<string>("FeaturesJson")
.HasColumnType("text");
.HasColumnType("ntext");
b.Property<string>("FeaturesSubtitleKey")
.HasMaxLength(256)
@ -2974,10 +2973,10 @@ namespace Sozsoft.Platform.Migrations
.HasColumnName("LastModifierId");
b.Property<string>("SlidesJson")
.HasColumnType("text");
.HasColumnType("ntext");
b.Property<string>("SolutionsJson")
.HasColumnType("text");
.HasColumnType("ntext");
b.Property<string>("SolutionsSubtitleKey")
.HasMaxLength(256)
@ -3180,40 +3179,40 @@ namespace Sozsoft.Platform.Migrations
.HasColumnType("uniqueidentifier");
b.Property<string>("AdaptiveLayoutJson")
.HasColumnType("text");
.HasColumnType("ntext");
b.Property<string>("AnimationJson")
.HasColumnType("text");
.HasColumnType("ntext");
b.Property<string>("AnnotationsJson")
.HasColumnType("text");
.HasColumnType("ntext");
b.Property<string>("ArgumentAxisJson")
.HasColumnType("text");
.HasColumnType("ntext");
b.Property<string>("ColumnOptionJson")
.HasColumnType("text");
.HasColumnType("ntext");
b.Property<string>("CommandColumnJson")
.HasColumnType("text");
.HasColumnType("ntext");
b.Property<string>("CommonAnnotationsSettingsJson")
.HasColumnType("text");
.HasColumnType("ntext");
b.Property<string>("CommonAxisSettingsJson")
.HasColumnType("text");
.HasColumnType("ntext");
b.Property<string>("CommonJson")
.HasColumnType("text");
.HasColumnType("ntext");
b.Property<string>("CommonPaneSettingsJson")
.HasColumnType("text");
.HasColumnType("ntext");
b.Property<string>("CommonSeriesSettingsJson")
.HasColumnType("text");
.HasColumnType("ntext");
b.Property<string>("CrosshairJson")
.HasColumnType("text");
.HasColumnType("ntext");
b.Property<string>("CultureName")
.IsRequired()
@ -3221,17 +3220,17 @@ namespace Sozsoft.Platform.Migrations
.HasColumnType("nvarchar(10)");
b.Property<string>("CustomJsSourcesJson")
.HasColumnType("text");
.HasColumnType("ntext");
b.Property<string>("CustomStyleSourcesJson")
.HasColumnType("text");
.HasColumnType("ntext");
b.Property<string>("DataSourceCode")
.HasMaxLength(128)
.HasColumnType("nvarchar(128)");
b.Property<string>("DataSourceJson")
.HasColumnType("text");
.HasColumnType("ntext");
b.Property<string>("DefaultFilter")
.IsUnicode(true)
@ -3244,10 +3243,10 @@ namespace Sozsoft.Platform.Migrations
.HasColumnType("nvarchar(max)");
b.Property<string>("DeleteCommand")
.HasColumnType("text");
.HasColumnType("ntext");
b.Property<string>("DeleteFieldsDefaultValueJson")
.HasColumnType("text");
.HasColumnType("ntext");
b.Property<string>("DeleteServiceAddress")
.HasMaxLength(256)
@ -3258,25 +3257,25 @@ namespace Sozsoft.Platform.Migrations
.HasColumnType("nvarchar(512)");
b.Property<string>("EditingFormJson")
.HasColumnType("text");
.HasColumnType("ntext");
b.Property<string>("EditingOptionJson")
.HasColumnType("text");
.HasColumnType("ntext");
b.Property<string>("ExportJson")
.HasColumnType("text");
.HasColumnType("ntext");
b.Property<string>("ExtraFilterJson")
.HasColumnType("text");
.HasColumnType("ntext");
b.Property<string>("FilterPanelJson")
.HasColumnType("text");
.HasColumnType("ntext");
b.Property<string>("FilterRowJson")
.HasColumnType("text");
.HasColumnType("ntext");
b.Property<string>("FormFieldsDefaultValueJson")
.HasColumnType("text");
.HasColumnType("ntext");
b.Property<bool>("FullHeight")
.HasColumnType("bit");
@ -3285,10 +3284,10 @@ namespace Sozsoft.Platform.Migrations
.HasColumnType("nvarchar(max)");
b.Property<string>("GroupPanelJson")
.HasColumnType("text");
.HasColumnType("ntext");
b.Property<string>("HeaderFilterJson")
.HasColumnType("text");
.HasColumnType("ntext");
b.Property<int?>("Height")
.HasColumnType("int");
@ -3300,10 +3299,10 @@ namespace Sozsoft.Platform.Migrations
.HasColumnType("nvarchar(max)");
b.Property<string>("InsertCommand")
.HasColumnType("text");
.HasColumnType("ntext");
b.Property<string>("InsertFieldsDefaultValueJson")
.HasColumnType("text");
.HasColumnType("ntext");
b.Property<string>("InsertServiceAddress")
.HasMaxLength(256)
@ -3329,10 +3328,10 @@ namespace Sozsoft.Platform.Migrations
.HasColumnType("nvarchar(64)");
b.Property<string>("LayoutJson")
.HasColumnType("text");
.HasColumnType("ntext");
b.Property<string>("LegendJson")
.HasColumnType("text");
.HasColumnType("ntext");
b.Property<string>("ListFormCode")
.IsRequired()
@ -3344,7 +3343,7 @@ namespace Sozsoft.Platform.Migrations
.HasColumnType("nvarchar(20)");
b.Property<string>("MarginJson")
.HasColumnType("text");
.HasColumnType("ntext");
b.Property<string>("Name")
.HasMaxLength(128)
@ -3356,16 +3355,16 @@ namespace Sozsoft.Platform.Migrations
.HasDefaultValue(50);
b.Property<string>("PagerOptionJson")
.HasColumnType("text");
.HasColumnType("ntext");
b.Property<string>("PanesJson")
.HasColumnType("text");
.HasColumnType("ntext");
b.Property<string>("PermissionJson")
.HasColumnType("text");
.HasColumnType("ntext");
b.Property<string>("PivotOptionJson")
.HasColumnType("text");
.HasColumnType("ntext");
b.Property<string>("RoleId")
.HasMaxLength(256)
@ -3378,41 +3377,41 @@ namespace Sozsoft.Platform.Migrations
.HasColumnType("nvarchar(max)");
b.Property<string>("ScrollBarJson")
.HasColumnType("text");
.HasColumnType("ntext");
b.Property<string>("SearchPanelJson")
.HasColumnType("text");
.HasColumnType("ntext");
b.Property<string>("SelectCommand")
.HasColumnType("text");
.HasColumnType("ntext");
b.Property<int>("SelectCommandType")
.HasColumnType("int");
b.Property<string>("SelectFieldsDefaultValueJson")
.HasColumnType("text");
.HasColumnType("ntext");
b.Property<string>("SelectionJson")
.HasColumnType("text");
.HasColumnType("ntext");
b.Property<string>("SeriesJson")
.HasColumnType("text");
.HasColumnType("ntext");
b.Property<bool>("ShowNote")
.HasColumnType("bit");
b.Property<string>("SizeJson")
.HasColumnType("text");
.HasColumnType("ntext");
b.Property<string>("SortMode")
.HasMaxLength(20)
.HasColumnType("nvarchar(20)");
b.Property<string>("StateStoringJson")
.HasColumnType("text");
.HasColumnType("ntext");
b.Property<string>("SubFormsJson")
.HasColumnType("text");
.HasColumnType("ntext");
b.Property<string>("SubFormsListFormType")
.HasColumnType("nvarchar(max)");
@ -3426,13 +3425,13 @@ namespace Sozsoft.Platform.Migrations
.HasColumnType("nvarchar(256)");
b.Property<string>("TitleJson")
.HasColumnType("text");
.HasColumnType("ntext");
b.Property<string>("TodoOptionJson")
.HasColumnType("text");
.HasColumnType("ntext");
b.Property<string>("TooltipJson")
.HasColumnType("text");
.HasColumnType("ntext");
b.Property<string>("TreeOptionJson")
.HasColumnType("nvarchar(max)");
@ -3444,10 +3443,10 @@ namespace Sozsoft.Platform.Migrations
.HasColumnType("nvarchar(max)");
b.Property<string>("UpdateCommand")
.HasColumnType("text");
.HasColumnType("ntext");
b.Property<string>("UpdateFieldsDefaultValueJson")
.HasColumnType("text");
.HasColumnType("ntext");
b.Property<string>("UpdateServiceAddress")
.HasMaxLength(256)
@ -3458,19 +3457,19 @@ namespace Sozsoft.Platform.Migrations
.HasColumnType("nvarchar(256)");
b.Property<string>("ValueAxisJson")
.HasColumnType("text");
.HasColumnType("ntext");
b.Property<string>("WidgetsJson")
.HasColumnType("text");
.HasColumnType("ntext");
b.Property<int?>("Width")
.HasColumnType("int");
b.Property<string>("WorkflowJson")
.HasColumnType("text");
.HasColumnType("ntext");
b.Property<string>("ZoomAndPanJson")
.HasColumnType("text");
.HasColumnType("ntext");
b.HasKey("Id");
@ -3494,7 +3493,7 @@ namespace Sozsoft.Platform.Migrations
.HasColumnName("CreatorId");
b.Property<string>("CustomizationData")
.HasColumnType("text");
.HasColumnType("ntext");
b.Property<int>("CustomizationType")
.HasColumnType("int");
@ -3591,16 +3590,16 @@ namespace Sozsoft.Platform.Migrations
.HasColumnType("nvarchar(256)");
b.Property<string>("ColumnCustomizationJson")
.HasColumnType("text");
.HasColumnType("ntext");
b.Property<string>("ColumnFilterJson")
.HasColumnType("text");
.HasColumnType("ntext");
b.Property<string>("ColumnHeaderJson")
.HasColumnType("text");
.HasColumnType("ntext");
b.Property<string>("ColumnStylingJson")
.HasColumnType("text");
.HasColumnType("ntext");
b.Property<string>("CultureName")
.IsRequired()
@ -3614,10 +3613,10 @@ namespace Sozsoft.Platform.Migrations
.HasColumnType("int");
b.Property<string>("EditorOptions")
.HasColumnType("text");
.HasColumnType("ntext");
b.Property<string>("EditorScript")
.HasColumnType("text");
.HasColumnType("ntext");
b.Property<string>("EditorType2")
.HasMaxLength(64)
@ -3633,10 +3632,10 @@ namespace Sozsoft.Platform.Migrations
.HasColumnType("nvarchar(128)");
b.Property<string>("GroupSummaryJson")
.HasColumnType("text");
.HasColumnType("ntext");
b.Property<string>("GroupingJson")
.HasColumnType("text");
.HasColumnType("ntext");
b.Property<bool?>("IsActive")
.ValueGeneratedOnAdd()
@ -3644,7 +3643,7 @@ namespace Sozsoft.Platform.Migrations
.HasDefaultValue(true);
b.Property<string>("JoinTableJson")
.HasColumnType("text");
.HasColumnType("ntext");
b.Property<string>("ListFormCode")
.IsRequired()
@ -3657,13 +3656,13 @@ namespace Sozsoft.Platform.Migrations
.HasDefaultValue(30);
b.Property<string>("LookupJson")
.HasColumnType("text");
.HasColumnType("ntext");
b.Property<string>("PermissionJson")
.HasColumnType("text");
.HasColumnType("ntext");
b.Property<string>("PivotSettingsJson")
.HasColumnType("text");
.HasColumnType("ntext");
b.Property<string>("PlaceHolder")
.HasMaxLength(256)
@ -3684,14 +3683,14 @@ namespace Sozsoft.Platform.Migrations
.HasColumnType("int");
b.Property<string>("TotalSummaryJson")
.HasColumnType("text");
.HasColumnType("ntext");
b.Property<string>("UserId")
.HasMaxLength(256)
.HasColumnType("nvarchar(256)");
b.Property<string>("ValidationRuleJson")
.HasColumnType("text");
.HasColumnType("ntext");
b.Property<bool?>("Visible")
.ValueGeneratedOnAdd()
@ -3801,7 +3800,7 @@ namespace Sozsoft.Platform.Migrations
.HasColumnType("int");
b.Property<string>("ErrorsJson")
.HasColumnType("text");
.HasColumnType("ntext");
b.Property<int>("ExecRows")
.HasColumnType("int");
@ -3863,7 +3862,7 @@ namespace Sozsoft.Platform.Migrations
.HasColumnType("nvarchar(20)");
b.Property<string>("CompareOutcomesJson")
.HasColumnType("text");
.HasColumnType("ntext");
b.Property<decimal>("CompareValue")
.HasPrecision(18, 2)
@ -4095,7 +4094,7 @@ namespace Sozsoft.Platform.Migrations
b.Property<string>("ParticipantIdsJson")
.IsRequired()
.HasColumnType("text");
.HasColumnType("ntext");
b.Property<string>("ParticipantKey")
.IsRequired()
@ -4122,7 +4121,7 @@ namespace Sozsoft.Platform.Migrations
b.Property<string>("AttachmentsJson")
.IsRequired()
.HasColumnType("text");
.HasColumnType("ntext");
b.Property<Guid>("ConversationId")
.HasColumnType("uniqueidentifier");
@ -4159,7 +4158,7 @@ namespace Sozsoft.Platform.Migrations
b.Property<string>("RecipientIdsJson")
.IsRequired()
.HasColumnType("text");
.HasColumnType("ntext");
b.Property<Guid>("SenderId")
.HasColumnType("uniqueidentifier");

View file

@ -489,12 +489,12 @@ namespace Sozsoft.Platform.Migrations
TenantId = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
UserName = table.Column<string>(type: "nvarchar(256)", maxLength: 256, nullable: false),
Text = table.Column<string>(type: "nvarchar(512)", maxLength: 512, nullable: false),
Description = table.Column<string>(type: "text", nullable: true),
Description = table.Column<string>(type: "ntext", nullable: true),
StartDate = table.Column<DateTime>(type: "datetime2", nullable: false),
EndDate = table.Column<DateTime>(type: "datetime2", nullable: false),
AllDay = table.Column<bool>(type: "bit", nullable: true, defaultValue: false),
RecurrenceRule = table.Column<string>(type: "nvarchar(512)", maxLength: 512, nullable: true),
RecurrenceException = table.Column<string>(type: "text", nullable: true),
RecurrenceException = table.Column<string>(type: "ntext", nullable: true),
CreationTime = table.Column<DateTime>(type: "datetime2", nullable: false),
CreatorId = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
LastModificationTime = table.Column<DateTime>(type: "datetime2", nullable: true),
@ -517,7 +517,7 @@ namespace Sozsoft.Platform.Migrations
Title = table.Column<string>(type: "nvarchar(256)", maxLength: 256, nullable: false),
Excerpt = table.Column<string>(type: "nvarchar(512)", maxLength: 512, nullable: false),
Content = table.Column<string>(type: "nvarchar(max)", maxLength: 4096, nullable: false),
ImageUrl = table.Column<string>(type: "text", nullable: true),
ImageUrl = table.Column<string>(type: "ntext", nullable: true),
Category = table.Column<string>(type: "nvarchar(64)", maxLength: 64, nullable: false),
UserId = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
PublishDate = table.Column<DateTime>(type: "datetime2", nullable: false),
@ -630,7 +630,7 @@ namespace Sozsoft.Platform.Migrations
TenantId = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
Title = table.Column<string>(type: "nvarchar(256)", maxLength: 256, nullable: true),
ParticipantKey = table.Column<string>(type: "nvarchar(512)", maxLength: 512, nullable: false),
ParticipantIdsJson = table.Column<string>(type: "text", nullable: false),
ParticipantIdsJson = table.Column<string>(type: "ntext", nullable: false),
IsGroup = table.Column<bool>(type: "bit", nullable: false),
LastSenderId = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
LastMessagePreview = table.Column<string>(type: "nvarchar(512)", maxLength: 512, nullable: true),
@ -926,9 +926,9 @@ namespace Sozsoft.Platform.Migrations
columns: table => new
{
Id = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
StatsJson = table.Column<string>(type: "text", nullable: true),
DescriptionsJson = table.Column<string>(type: "text", nullable: true),
SectionsJson = table.Column<string>(type: "text", nullable: true),
StatsJson = table.Column<string>(type: "ntext", nullable: true),
DescriptionsJson = table.Column<string>(type: "ntext", nullable: true),
SectionsJson = table.Column<string>(type: "ntext", nullable: true),
CreationTime = table.Column<DateTime>(type: "datetime2", nullable: false),
CreatorId = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
LastModificationTime = table.Column<DateTime>(type: "datetime2", nullable: true),
@ -950,8 +950,8 @@ namespace Sozsoft.Platform.Migrations
Name = table.Column<string>(type: "nvarchar(128)", maxLength: 128, nullable: false),
IsActive = table.Column<bool>(type: "bit", nullable: false),
ApiUrl = table.Column<string>(type: "nvarchar(256)", maxLength: 256, nullable: true),
Description = table.Column<string>(type: "text", nullable: true),
Tenants = table.Column<string>(type: "text", nullable: true)
Description = table.Column<string>(type: "ntext", nullable: true),
Tenants = table.Column<string>(type: "ntext", nullable: true)
},
constraints: table =>
{
@ -970,7 +970,7 @@ namespace Sozsoft.Platform.Migrations
DataSourceCode = table.Column<string>(type: "nvarchar(64)", maxLength: 64, nullable: true),
BeforeSp = table.Column<string>(type: "nvarchar(128)", maxLength: 128, nullable: true),
AfterSp = table.Column<string>(type: "nvarchar(128)", maxLength: 128, nullable: true),
Options = table.Column<string>(type: "text", nullable: true),
Options = table.Column<string>(type: "ntext", nullable: true),
CreationTime = table.Column<DateTime>(type: "datetime2", nullable: false),
CreatorId = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
LastModificationTime = table.Column<DateTime>(type: "datetime2", nullable: true),
@ -1045,9 +1045,9 @@ namespace Sozsoft.Platform.Migrations
Email = table.Column<string>(type: "nvarchar(128)", maxLength: 128, nullable: true),
Location = table.Column<string>(type: "nvarchar(256)", maxLength: 256, nullable: true),
TaxNumber = table.Column<long>(type: "bigint", nullable: true),
BankJson = table.Column<string>(type: "text", nullable: true),
WorkHoursJson = table.Column<string>(type: "text", nullable: true),
MapJson = table.Column<string>(type: "text", nullable: true),
BankJson = table.Column<string>(type: "ntext", nullable: true),
WorkHoursJson = table.Column<string>(type: "ntext", nullable: true),
MapJson = table.Column<string>(type: "ntext", nullable: true),
CreationTime = table.Column<DateTime>(type: "datetime2", nullable: false),
CreatorId = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
LastModificationTime = table.Column<DateTime>(type: "datetime2", nullable: true),
@ -1158,7 +1158,7 @@ namespace Sozsoft.Platform.Migrations
Name = table.Column<string>(type: "nvarchar(128)", maxLength: 128, nullable: false),
RoutePath = table.Column<string>(type: "nvarchar(512)", maxLength: 512, nullable: false),
Code = table.Column<string>(type: "nvarchar(max)", nullable: false),
Props = table.Column<string>(type: "nvarchar(1024)", maxLength: 1024, nullable: true),
Props = table.Column<string>(type: "nvarchar(max)", nullable: true),
Description = table.Column<string>(type: "nvarchar(512)", maxLength: 512, nullable: true),
IsActive = table.Column<bool>(type: "bit", nullable: false),
Dependencies = table.Column<string>(type: "nvarchar(2048)", maxLength: 2048, nullable: true),
@ -1187,8 +1187,8 @@ namespace Sozsoft.Platform.Migrations
Method = table.Column<string>(type: "nvarchar(10)", maxLength: 10, nullable: false),
DataSourceCode = table.Column<string>(type: "nvarchar(128)", maxLength: 128, nullable: false),
Sql = table.Column<string>(type: "nvarchar(max)", nullable: false),
ParametersJson = table.Column<string>(type: "text", nullable: true),
PermissionsJson = table.Column<string>(type: "text", nullable: true),
ParametersJson = table.Column<string>(type: "ntext", nullable: true),
PermissionsJson = table.Column<string>(type: "ntext", nullable: true),
CreationTime = table.Column<DateTime>(type: "datetime2", nullable: false),
CreatorId = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
LastModificationTime = table.Column<DateTime>(type: "datetime2", nullable: true),
@ -1237,7 +1237,7 @@ namespace Sozsoft.Platform.Migrations
ResourceName = table.Column<string>(type: "nvarchar(64)", maxLength: 64, nullable: true),
MaxForms = table.Column<int>(type: "int", nullable: false, defaultValue: 500),
CacheKey = table.Column<string>(type: "nvarchar(512)", maxLength: 512, nullable: false),
JsonData = table.Column<string>(type: "text", nullable: false),
JsonData = table.Column<string>(type: "ntext", nullable: false),
FormCount = table.Column<int>(type: "int", nullable: false, defaultValue: 0),
FieldCount = table.Column<int>(type: "int", nullable: false, defaultValue: 0),
JsonLength = table.Column<int>(type: "int", nullable: false, defaultValue: 0),
@ -1287,7 +1287,7 @@ namespace Sozsoft.Platform.Migrations
RequestDate = table.Column<DateTime>(type: "datetime2", nullable: true),
Title = table.Column<string>(type: "nvarchar(512)", maxLength: 512, nullable: false),
Status = table.Column<string>(type: "nvarchar(64)", maxLength: 64, nullable: false),
Description = table.Column<string>(type: "text", nullable: true),
Description = table.Column<string>(type: "ntext", nullable: true),
DueDate = table.Column<DateTime>(type: "datetime2", nullable: true),
Tags = table.Column<string>(type: "nvarchar(256)", maxLength: 256, nullable: true),
Assignees = table.Column<string>(type: "nvarchar(512)", maxLength: 512, nullable: true),
@ -1316,10 +1316,10 @@ namespace Sozsoft.Platform.Migrations
Name = table.Column<string>(type: "nvarchar(256)", maxLength: 256, nullable: false),
DisplayName = table.Column<string>(type: "nvarchar(512)", maxLength: 512, nullable: true),
Description = table.Column<string>(type: "nvarchar(2048)", maxLength: 2048, nullable: true),
Code = table.Column<string>(type: "text", nullable: false),
Code = table.Column<string>(type: "ntext", nullable: false),
IsActive = table.Column<bool>(type: "bit", nullable: false, defaultValue: true),
CompilationStatus = table.Column<string>(type: "nvarchar(20)", maxLength: 20, nullable: false),
LastCompilationError = table.Column<string>(type: "text", nullable: true),
LastCompilationError = table.Column<string>(type: "ntext", nullable: true),
LastSuccessfulCompilation = table.Column<DateTime>(type: "datetime2", nullable: true),
Version = table.Column<int>(type: "int", nullable: false, defaultValue: 1),
CodeHash = table.Column<string>(type: "nvarchar(64)", maxLength: 64, nullable: true),
@ -1353,9 +1353,9 @@ namespace Sozsoft.Platform.Migrations
CtaTitleKey = table.Column<string>(type: "nvarchar(256)", maxLength: 256, nullable: true),
CtaSubtitleKey = table.Column<string>(type: "nvarchar(256)", maxLength: 256, nullable: true),
CtaButtonLabelKey = table.Column<string>(type: "nvarchar(256)", maxLength: 256, nullable: true),
SlidesJson = table.Column<string>(type: "text", nullable: true),
FeaturesJson = table.Column<string>(type: "text", nullable: true),
SolutionsJson = table.Column<string>(type: "text", nullable: true),
SlidesJson = table.Column<string>(type: "ntext", nullable: true),
FeaturesJson = table.Column<string>(type: "ntext", nullable: true),
SolutionsJson = table.Column<string>(type: "ntext", nullable: true),
CreationTime = table.Column<DateTime>(type: "datetime2", nullable: false),
CreatorId = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
LastModificationTime = table.Column<DateTime>(type: "datetime2", nullable: true),
@ -1432,10 +1432,10 @@ namespace Sozsoft.Platform.Migrations
DataSourceCode = table.Column<string>(type: "nvarchar(128)", maxLength: 128, nullable: true),
SelectCommandType = table.Column<int>(type: "int", nullable: false),
TableName = table.Column<string>(type: "nvarchar(128)", maxLength: 128, nullable: true),
SelectCommand = table.Column<string>(type: "text", nullable: true),
SelectCommand = table.Column<string>(type: "ntext", nullable: true),
KeyFieldName = table.Column<string>(type: "nvarchar(64)", maxLength: 64, nullable: true),
KeyFieldDbSourceType = table.Column<int>(type: "int", nullable: false),
SelectFieldsDefaultValueJson = table.Column<string>(type: "text", nullable: true),
SelectFieldsDefaultValueJson = table.Column<string>(type: "ntext", nullable: true),
Name = table.Column<string>(type: "nvarchar(128)", maxLength: 128, nullable: true),
Description = table.Column<string>(type: "nvarchar(512)", maxLength: 512, nullable: true),
Title = table.Column<string>(type: "nvarchar(256)", maxLength: 256, nullable: true),
@ -1445,43 +1445,43 @@ namespace Sozsoft.Platform.Migrations
Height = table.Column<int>(type: "int", nullable: true),
FullHeight = table.Column<bool>(type: "bit", nullable: false),
DefaultFilter = table.Column<string>(type: "nvarchar(max)", nullable: true),
ColumnOptionJson = table.Column<string>(type: "text", nullable: true),
PivotOptionJson = table.Column<string>(type: "text", nullable: true),
ColumnOptionJson = table.Column<string>(type: "ntext", nullable: true),
PivotOptionJson = table.Column<string>(type: "ntext", nullable: true),
TreeOptionJson = table.Column<string>(type: "nvarchar(max)", nullable: true),
GanttOptionJson = table.Column<string>(type: "nvarchar(max)", nullable: true),
SchedulerOptionJson = table.Column<string>(type: "nvarchar(max)", nullable: true),
TodoOptionJson = table.Column<string>(type: "text", nullable: true),
FilterRowJson = table.Column<string>(type: "text", nullable: true),
TodoOptionJson = table.Column<string>(type: "ntext", nullable: true),
FilterRowJson = table.Column<string>(type: "ntext", nullable: true),
RowJson = table.Column<string>(type: "nvarchar(max)", nullable: true),
HeaderFilterJson = table.Column<string>(type: "text", nullable: true),
FilterPanelJson = table.Column<string>(type: "text", nullable: true),
SearchPanelJson = table.Column<string>(type: "text", nullable: true),
GroupPanelJson = table.Column<string>(type: "text", nullable: true),
SelectionJson = table.Column<string>(type: "text", nullable: true),
PagerOptionJson = table.Column<string>(type: "text", nullable: true),
EditingOptionJson = table.Column<string>(type: "text", nullable: true),
EditingFormJson = table.Column<string>(type: "text", nullable: true),
PermissionJson = table.Column<string>(type: "text", nullable: true),
InsertFieldsDefaultValueJson = table.Column<string>(type: "text", nullable: true),
UpdateFieldsDefaultValueJson = table.Column<string>(type: "text", nullable: true),
DeleteFieldsDefaultValueJson = table.Column<string>(type: "text", nullable: true),
CommandColumnJson = table.Column<string>(type: "text", nullable: true),
StateStoringJson = table.Column<string>(type: "text", nullable: true),
DeleteCommand = table.Column<string>(type: "text", nullable: true),
HeaderFilterJson = table.Column<string>(type: "ntext", nullable: true),
FilterPanelJson = table.Column<string>(type: "ntext", nullable: true),
SearchPanelJson = table.Column<string>(type: "ntext", nullable: true),
GroupPanelJson = table.Column<string>(type: "ntext", nullable: true),
SelectionJson = table.Column<string>(type: "ntext", nullable: true),
PagerOptionJson = table.Column<string>(type: "ntext", nullable: true),
EditingOptionJson = table.Column<string>(type: "ntext", nullable: true),
EditingFormJson = table.Column<string>(type: "ntext", nullable: true),
PermissionJson = table.Column<string>(type: "ntext", nullable: true),
InsertFieldsDefaultValueJson = table.Column<string>(type: "ntext", nullable: true),
UpdateFieldsDefaultValueJson = table.Column<string>(type: "ntext", nullable: true),
DeleteFieldsDefaultValueJson = table.Column<string>(type: "ntext", nullable: true),
CommandColumnJson = table.Column<string>(type: "ntext", nullable: true),
StateStoringJson = table.Column<string>(type: "ntext", nullable: true),
DeleteCommand = table.Column<string>(type: "ntext", nullable: true),
DeleteBeforeCommand = table.Column<string>(type: "nvarchar(max)", nullable: true),
DeleteAfterCommand = table.Column<string>(type: "nvarchar(max)", nullable: true),
UpdateCommand = table.Column<string>(type: "text", nullable: true),
UpdateCommand = table.Column<string>(type: "ntext", nullable: true),
UpdateBeforeCommand = table.Column<string>(type: "nvarchar(max)", nullable: true),
UpdateAfterCommand = table.Column<string>(type: "nvarchar(max)", nullable: true),
InsertCommand = table.Column<string>(type: "text", nullable: true),
InsertCommand = table.Column<string>(type: "ntext", nullable: true),
InsertBeforeCommand = table.Column<string>(type: "nvarchar(max)", nullable: true),
InsertAfterCommand = table.Column<string>(type: "nvarchar(max)", nullable: true),
UpdateServiceAddress = table.Column<string>(type: "nvarchar(256)", maxLength: 256, nullable: true),
InsertServiceAddress = table.Column<string>(type: "nvarchar(256)", maxLength: 256, nullable: true),
DeleteServiceAddress = table.Column<string>(type: "nvarchar(256)", maxLength: 256, nullable: true),
CustomJsSourcesJson = table.Column<string>(type: "text", nullable: true),
CustomStyleSourcesJson = table.Column<string>(type: "text", nullable: true),
FormFieldsDefaultValueJson = table.Column<string>(type: "text", nullable: true),
CustomJsSourcesJson = table.Column<string>(type: "ntext", nullable: true),
CustomStyleSourcesJson = table.Column<string>(type: "ntext", nullable: true),
FormFieldsDefaultValueJson = table.Column<string>(type: "ntext", nullable: true),
IsTenant = table.Column<bool>(type: "bit", nullable: false),
IsBranch = table.Column<bool>(type: "bit", nullable: false),
IsOrganizationUnit = table.Column<bool>(type: "bit", nullable: false),
@ -1489,35 +1489,35 @@ namespace Sozsoft.Platform.Migrations
IsSubForm = table.Column<bool>(type: "bit", nullable: false),
SubFormsListFormType = table.Column<string>(type: "nvarchar(max)", nullable: true),
ShowNote = table.Column<bool>(type: "bit", nullable: false),
SubFormsJson = table.Column<string>(type: "text", nullable: true),
WidgetsJson = table.Column<string>(type: "text", nullable: true),
WorkflowJson = table.Column<string>(type: "text", nullable: true),
ExtraFilterJson = table.Column<string>(type: "text", nullable: true),
LayoutJson = table.Column<string>(type: "text", nullable: true),
SubFormsJson = table.Column<string>(type: "ntext", nullable: true),
WidgetsJson = table.Column<string>(type: "ntext", nullable: true),
WorkflowJson = table.Column<string>(type: "ntext", nullable: true),
ExtraFilterJson = table.Column<string>(type: "ntext", nullable: true),
LayoutJson = table.Column<string>(type: "ntext", nullable: true),
UserId = table.Column<string>(type: "nvarchar(256)", maxLength: 256, nullable: true),
RoleId = table.Column<string>(type: "nvarchar(256)", maxLength: 256, nullable: true),
CommonJson = table.Column<string>(type: "text", nullable: true),
DataSourceJson = table.Column<string>(type: "text", nullable: true),
AdaptiveLayoutJson = table.Column<string>(type: "text", nullable: true),
AnimationJson = table.Column<string>(type: "text", nullable: true),
AnnotationsJson = table.Column<string>(type: "text", nullable: true),
ArgumentAxisJson = table.Column<string>(type: "text", nullable: true),
CommonAnnotationsSettingsJson = table.Column<string>(type: "text", nullable: true),
CommonAxisSettingsJson = table.Column<string>(type: "text", nullable: true),
CommonPaneSettingsJson = table.Column<string>(type: "text", nullable: true),
CommonSeriesSettingsJson = table.Column<string>(type: "text", nullable: true),
CrosshairJson = table.Column<string>(type: "text", nullable: true),
ExportJson = table.Column<string>(type: "text", nullable: true),
LegendJson = table.Column<string>(type: "text", nullable: true),
MarginJson = table.Column<string>(type: "text", nullable: true),
PanesJson = table.Column<string>(type: "text", nullable: true),
ScrollBarJson = table.Column<string>(type: "text", nullable: true),
SeriesJson = table.Column<string>(type: "text", nullable: true),
SizeJson = table.Column<string>(type: "text", nullable: true),
TitleJson = table.Column<string>(type: "text", nullable: true),
TooltipJson = table.Column<string>(type: "text", nullable: true),
ValueAxisJson = table.Column<string>(type: "text", nullable: true),
ZoomAndPanJson = table.Column<string>(type: "text", nullable: true)
CommonJson = table.Column<string>(type: "ntext", nullable: true),
DataSourceJson = table.Column<string>(type: "ntext", nullable: true),
AdaptiveLayoutJson = table.Column<string>(type: "ntext", nullable: true),
AnimationJson = table.Column<string>(type: "ntext", nullable: true),
AnnotationsJson = table.Column<string>(type: "ntext", nullable: true),
ArgumentAxisJson = table.Column<string>(type: "ntext", nullable: true),
CommonAnnotationsSettingsJson = table.Column<string>(type: "ntext", nullable: true),
CommonAxisSettingsJson = table.Column<string>(type: "ntext", nullable: true),
CommonPaneSettingsJson = table.Column<string>(type: "ntext", nullable: true),
CommonSeriesSettingsJson = table.Column<string>(type: "ntext", nullable: true),
CrosshairJson = table.Column<string>(type: "ntext", nullable: true),
ExportJson = table.Column<string>(type: "ntext", nullable: true),
LegendJson = table.Column<string>(type: "ntext", nullable: true),
MarginJson = table.Column<string>(type: "ntext", nullable: true),
PanesJson = table.Column<string>(type: "ntext", nullable: true),
ScrollBarJson = table.Column<string>(type: "ntext", nullable: true),
SeriesJson = table.Column<string>(type: "ntext", nullable: true),
SizeJson = table.Column<string>(type: "ntext", nullable: true),
TitleJson = table.Column<string>(type: "ntext", nullable: true),
TooltipJson = table.Column<string>(type: "ntext", nullable: true),
ValueAxisJson = table.Column<string>(type: "ntext", nullable: true),
ZoomAndPanJson = table.Column<string>(type: "ntext", nullable: true)
},
constraints: table =>
{
@ -2250,7 +2250,7 @@ namespace Sozsoft.Platform.Migrations
ParticipantsCount = table.Column<int>(type: "int", nullable: false),
Likes = table.Column<int>(type: "int", nullable: false),
isPublished = table.Column<bool>(type: "bit", nullable: false, defaultValue: false),
Photos = table.Column<string>(type: "text", nullable: true),
Photos = table.Column<string>(type: "ntext", nullable: true),
CreationTime = table.Column<DateTime>(type: "datetime2", nullable: false),
CreatorId = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
LastModificationTime = table.Column<DateTime>(type: "datetime2", nullable: true),
@ -2286,9 +2286,9 @@ namespace Sozsoft.Platform.Migrations
SenderId = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
SenderUserName = table.Column<string>(type: "nvarchar(256)", maxLength: 256, nullable: false),
SenderName = table.Column<string>(type: "nvarchar(256)", maxLength: 256, nullable: false),
RecipientIdsJson = table.Column<string>(type: "text", nullable: false),
RecipientIdsJson = table.Column<string>(type: "ntext", nullable: false),
Text = table.Column<string>(type: "nvarchar(max)", maxLength: 4096, nullable: true),
AttachmentsJson = table.Column<string>(type: "text", nullable: false),
AttachmentsJson = table.Column<string>(type: "ntext", nullable: false),
SentAt = table.Column<DateTime>(type: "datetime2", nullable: false),
CreationTime = table.Column<DateTime>(type: "datetime2", nullable: false),
CreatorId = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
@ -2789,7 +2789,7 @@ namespace Sozsoft.Platform.Migrations
UserId = table.Column<string>(type: "nvarchar(256)", maxLength: 256, nullable: true),
RoleId = table.Column<string>(type: "nvarchar(256)", maxLength: 256, nullable: true),
FilterName = table.Column<string>(type: "nvarchar(128)", maxLength: 128, nullable: true),
CustomizationData = table.Column<string>(type: "text", nullable: true),
CustomizationData = table.Column<string>(type: "ntext", nullable: true),
CustomizationType = table.Column<int>(type: "int", nullable: false),
CreationTime = table.Column<DateTime>(type: "datetime2", nullable: false),
CreatorId = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
@ -2833,28 +2833,28 @@ namespace Sozsoft.Platform.Migrations
AllowEditing = table.Column<bool>(type: "bit", nullable: true, defaultValue: true),
AllowAdding = table.Column<bool>(type: "bit", nullable: true, defaultValue: true),
BandName = table.Column<string>(type: "nvarchar(128)", maxLength: 128, nullable: true),
ColumnFilterJson = table.Column<string>(type: "text", nullable: true),
ColumnHeaderJson = table.Column<string>(type: "text", nullable: true),
GroupingJson = table.Column<string>(type: "text", nullable: true),
ColumnCustomizationJson = table.Column<string>(type: "text", nullable: true),
TotalSummaryJson = table.Column<string>(type: "text", nullable: true),
GroupSummaryJson = table.Column<string>(type: "text", nullable: true),
ColumnFilterJson = table.Column<string>(type: "ntext", nullable: true),
ColumnHeaderJson = table.Column<string>(type: "ntext", nullable: true),
GroupingJson = table.Column<string>(type: "ntext", nullable: true),
ColumnCustomizationJson = table.Column<string>(type: "ntext", nullable: true),
TotalSummaryJson = table.Column<string>(type: "ntext", nullable: true),
GroupSummaryJson = table.Column<string>(type: "ntext", nullable: true),
ColumnCssClass = table.Column<string>(type: "nvarchar(128)", maxLength: 128, nullable: true),
ColumnCssValue = table.Column<string>(type: "nvarchar(256)", maxLength: 256, nullable: true),
JoinTableJson = table.Column<string>(type: "text", nullable: true),
LookupJson = table.Column<string>(type: "text", nullable: true),
ValidationRuleJson = table.Column<string>(type: "text", nullable: true),
ColumnStylingJson = table.Column<string>(type: "text", nullable: true),
PermissionJson = table.Column<string>(type: "text", nullable: true),
PivotSettingsJson = table.Column<string>(type: "text", nullable: true),
JoinTableJson = table.Column<string>(type: "ntext", nullable: true),
LookupJson = table.Column<string>(type: "ntext", nullable: true),
ValidationRuleJson = table.Column<string>(type: "ntext", nullable: true),
ColumnStylingJson = table.Column<string>(type: "ntext", nullable: true),
PermissionJson = table.Column<string>(type: "ntext", nullable: true),
PivotSettingsJson = table.Column<string>(type: "ntext", nullable: true),
Alignment = table.Column<string>(type: "nvarchar(20)", maxLength: 20, nullable: true, defaultValue: "left"),
Format = table.Column<string>(type: "nvarchar(128)", maxLength: 128, nullable: true),
EditOrderNo = table.Column<int>(type: "int", nullable: true),
EditGroupOrderNo = table.Column<int>(type: "int", nullable: true),
EditorType2 = table.Column<string>(type: "nvarchar(64)", maxLength: 64, nullable: true),
ColSpan = table.Column<int>(type: "int", nullable: true),
EditorScript = table.Column<string>(type: "text", nullable: true),
EditorOptions = table.Column<string>(type: "text", nullable: true)
EditorScript = table.Column<string>(type: "ntext", nullable: true),
EditorOptions = table.Column<string>(type: "ntext", nullable: true)
},
constraints: table =>
{
@ -2914,7 +2914,7 @@ namespace Sozsoft.Platform.Migrations
NextOnReject = table.Column<string>(type: "nvarchar(128)", maxLength: 128, nullable: false),
PositionX = table.Column<int>(type: "int", nullable: false),
PositionY = table.Column<int>(type: "int", nullable: false),
CompareOutcomesJson = table.Column<string>(type: "text", nullable: true)
CompareOutcomesJson = table.Column<string>(type: "ntext", nullable: true)
},
constraints: table =>
{
@ -3392,7 +3392,7 @@ namespace Sozsoft.Platform.Migrations
ValidRows = table.Column<int>(type: "int", nullable: false),
ErrorRows = table.Column<int>(type: "int", nullable: false),
Progress = table.Column<double>(type: "float", nullable: false),
ErrorsJson = table.Column<string>(type: "text", nullable: true),
ErrorsJson = table.Column<string>(type: "ntext", nullable: true),
CreationTime = table.Column<DateTime>(type: "datetime2", nullable: false),
CreatorId = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
LastModificationTime = table.Column<DateTime>(type: "datetime2", nullable: true),

View file

@ -649,7 +649,7 @@ namespace Sozsoft.Platform.Migrations
.HasColumnName("DeletionTime");
b.Property<string>("DescriptionsJson")
.HasColumnType("text");
.HasColumnType("ntext");
b.Property<bool>("IsDeleted")
.ValueGeneratedOnAdd()
@ -666,10 +666,10 @@ namespace Sozsoft.Platform.Migrations
.HasColumnName("LastModifierId");
b.Property<string>("SectionsJson")
.HasColumnType("text");
.HasColumnType("ntext");
b.Property<string>("StatsJson")
.HasColumnType("text");
.HasColumnType("ntext");
b.HasKey("Id");
@ -703,7 +703,7 @@ namespace Sozsoft.Platform.Migrations
.HasColumnName("DeletionTime");
b.Property<string>("Description")
.HasColumnType("text");
.HasColumnType("ntext");
b.Property<DateTime>("EndDate")
.HasColumnType("datetime2");
@ -723,7 +723,7 @@ namespace Sozsoft.Platform.Migrations
.HasColumnName("LastModifierId");
b.Property<string>("RecurrenceException")
.HasColumnType("text");
.HasColumnType("ntext");
b.Property<string>("RecurrenceRule")
.HasMaxLength(512)
@ -761,7 +761,7 @@ namespace Sozsoft.Platform.Migrations
.HasColumnType("nvarchar(256)");
b.Property<string>("Description")
.HasColumnType("text");
.HasColumnType("ntext");
b.Property<bool>("IsActive")
.HasColumnType("bit");
@ -772,7 +772,7 @@ namespace Sozsoft.Platform.Migrations
.HasColumnType("nvarchar(128)");
b.Property<string>("Tenants")
.HasColumnType("text");
.HasColumnType("ntext");
b.HasKey("Id");
@ -826,7 +826,7 @@ namespace Sozsoft.Platform.Migrations
.HasColumnType("datetime2");
b.Property<string>("ImageUrl")
.HasColumnType("text");
.HasColumnType("ntext");
b.Property<bool>("IsDeleted")
.ValueGeneratedOnAdd()
@ -1056,7 +1056,7 @@ namespace Sozsoft.Platform.Migrations
.HasColumnType("nvarchar(128)");
b.Property<string>("Options")
.HasColumnType("text");
.HasColumnType("ntext");
b.Property<int>("WorkerType")
.HasColumnType("int");
@ -1450,7 +1450,7 @@ namespace Sozsoft.Platform.Migrations
.HasColumnType("nvarchar(512)");
b.Property<string>("BankJson")
.HasColumnType("text");
.HasColumnType("ntext");
b.Property<DateTime>("CreationTime")
.HasColumnType("datetime2")
@ -1491,7 +1491,7 @@ namespace Sozsoft.Platform.Migrations
.HasColumnType("nvarchar(256)");
b.Property<string>("MapJson")
.HasColumnType("text");
.HasColumnType("ntext");
b.Property<string>("PhoneNumber")
.HasMaxLength(20)
@ -1501,7 +1501,7 @@ namespace Sozsoft.Platform.Migrations
.HasColumnType("bigint");
b.Property<string>("WorkHoursJson")
.HasColumnType("text");
.HasColumnType("ntext");
b.HasKey("Id");
@ -1895,8 +1895,7 @@ namespace Sozsoft.Platform.Migrations
.HasColumnType("nvarchar(128)");
b.Property<string>("Props")
.HasMaxLength(1024)
.HasColumnType("nvarchar(1024)");
.HasColumnType("nvarchar(max)");
b.Property<string>("RoutePath")
.IsRequired()
@ -1975,10 +1974,10 @@ namespace Sozsoft.Platform.Migrations
.HasColumnType("nvarchar(128)");
b.Property<string>("ParametersJson")
.HasColumnType("text");
.HasColumnType("ntext");
b.Property<string>("PermissionsJson")
.HasColumnType("text");
.HasColumnType("ntext");
b.Property<string>("Sql")
.IsRequired()
@ -2100,7 +2099,7 @@ namespace Sozsoft.Platform.Migrations
b.Property<string>("JsonData")
.IsRequired()
.HasColumnType("text");
.HasColumnType("ntext");
b.Property<int>("JsonLength")
.ValueGeneratedOnAdd()
@ -2320,7 +2319,7 @@ namespace Sozsoft.Platform.Migrations
.HasColumnName("DeletionTime");
b.Property<string>("Description")
.HasColumnType("text");
.HasColumnType("ntext");
b.Property<DateTime?>("DueDate")
.HasColumnType("datetime2");
@ -2455,7 +2454,7 @@ namespace Sozsoft.Platform.Migrations
b.Property<string>("Code")
.IsRequired()
.HasColumnType("text");
.HasColumnType("ntext");
b.Property<string>("CodeHash")
.HasMaxLength(64)
@ -2506,7 +2505,7 @@ namespace Sozsoft.Platform.Migrations
.HasColumnName("IsDeleted");
b.Property<string>("LastCompilationError")
.HasColumnType("text");
.HasColumnType("ntext");
b.Property<DateTime?>("LastModificationTime")
.HasColumnType("datetime2")
@ -2603,7 +2602,7 @@ namespace Sozsoft.Platform.Migrations
.HasColumnType("int");
b.Property<string>("Photos")
.HasColumnType("text");
.HasColumnType("ntext");
b.Property<string>("Place")
.HasMaxLength(256)
@ -2934,7 +2933,7 @@ namespace Sozsoft.Platform.Migrations
.HasColumnName("DeletionTime");
b.Property<string>("FeaturesJson")
.HasColumnType("text");
.HasColumnType("ntext");
b.Property<string>("FeaturesSubtitleKey")
.HasMaxLength(256)
@ -2971,10 +2970,10 @@ namespace Sozsoft.Platform.Migrations
.HasColumnName("LastModifierId");
b.Property<string>("SlidesJson")
.HasColumnType("text");
.HasColumnType("ntext");
b.Property<string>("SolutionsJson")
.HasColumnType("text");
.HasColumnType("ntext");
b.Property<string>("SolutionsSubtitleKey")
.HasMaxLength(256)
@ -3177,40 +3176,40 @@ namespace Sozsoft.Platform.Migrations
.HasColumnType("uniqueidentifier");
b.Property<string>("AdaptiveLayoutJson")
.HasColumnType("text");
.HasColumnType("ntext");
b.Property<string>("AnimationJson")
.HasColumnType("text");
.HasColumnType("ntext");
b.Property<string>("AnnotationsJson")
.HasColumnType("text");
.HasColumnType("ntext");
b.Property<string>("ArgumentAxisJson")
.HasColumnType("text");
.HasColumnType("ntext");
b.Property<string>("ColumnOptionJson")
.HasColumnType("text");
.HasColumnType("ntext");
b.Property<string>("CommandColumnJson")
.HasColumnType("text");
.HasColumnType("ntext");
b.Property<string>("CommonAnnotationsSettingsJson")
.HasColumnType("text");
.HasColumnType("ntext");
b.Property<string>("CommonAxisSettingsJson")
.HasColumnType("text");
.HasColumnType("ntext");
b.Property<string>("CommonJson")
.HasColumnType("text");
.HasColumnType("ntext");
b.Property<string>("CommonPaneSettingsJson")
.HasColumnType("text");
.HasColumnType("ntext");
b.Property<string>("CommonSeriesSettingsJson")
.HasColumnType("text");
.HasColumnType("ntext");
b.Property<string>("CrosshairJson")
.HasColumnType("text");
.HasColumnType("ntext");
b.Property<string>("CultureName")
.IsRequired()
@ -3218,17 +3217,17 @@ namespace Sozsoft.Platform.Migrations
.HasColumnType("nvarchar(10)");
b.Property<string>("CustomJsSourcesJson")
.HasColumnType("text");
.HasColumnType("ntext");
b.Property<string>("CustomStyleSourcesJson")
.HasColumnType("text");
.HasColumnType("ntext");
b.Property<string>("DataSourceCode")
.HasMaxLength(128)
.HasColumnType("nvarchar(128)");
b.Property<string>("DataSourceJson")
.HasColumnType("text");
.HasColumnType("ntext");
b.Property<string>("DefaultFilter")
.IsUnicode(true)
@ -3241,10 +3240,10 @@ namespace Sozsoft.Platform.Migrations
.HasColumnType("nvarchar(max)");
b.Property<string>("DeleteCommand")
.HasColumnType("text");
.HasColumnType("ntext");
b.Property<string>("DeleteFieldsDefaultValueJson")
.HasColumnType("text");
.HasColumnType("ntext");
b.Property<string>("DeleteServiceAddress")
.HasMaxLength(256)
@ -3255,25 +3254,25 @@ namespace Sozsoft.Platform.Migrations
.HasColumnType("nvarchar(512)");
b.Property<string>("EditingFormJson")
.HasColumnType("text");
.HasColumnType("ntext");
b.Property<string>("EditingOptionJson")
.HasColumnType("text");
.HasColumnType("ntext");
b.Property<string>("ExportJson")
.HasColumnType("text");
.HasColumnType("ntext");
b.Property<string>("ExtraFilterJson")
.HasColumnType("text");
.HasColumnType("ntext");
b.Property<string>("FilterPanelJson")
.HasColumnType("text");
.HasColumnType("ntext");
b.Property<string>("FilterRowJson")
.HasColumnType("text");
.HasColumnType("ntext");
b.Property<string>("FormFieldsDefaultValueJson")
.HasColumnType("text");
.HasColumnType("ntext");
b.Property<bool>("FullHeight")
.HasColumnType("bit");
@ -3282,10 +3281,10 @@ namespace Sozsoft.Platform.Migrations
.HasColumnType("nvarchar(max)");
b.Property<string>("GroupPanelJson")
.HasColumnType("text");
.HasColumnType("ntext");
b.Property<string>("HeaderFilterJson")
.HasColumnType("text");
.HasColumnType("ntext");
b.Property<int?>("Height")
.HasColumnType("int");
@ -3297,10 +3296,10 @@ namespace Sozsoft.Platform.Migrations
.HasColumnType("nvarchar(max)");
b.Property<string>("InsertCommand")
.HasColumnType("text");
.HasColumnType("ntext");
b.Property<string>("InsertFieldsDefaultValueJson")
.HasColumnType("text");
.HasColumnType("ntext");
b.Property<string>("InsertServiceAddress")
.HasMaxLength(256)
@ -3326,10 +3325,10 @@ namespace Sozsoft.Platform.Migrations
.HasColumnType("nvarchar(64)");
b.Property<string>("LayoutJson")
.HasColumnType("text");
.HasColumnType("ntext");
b.Property<string>("LegendJson")
.HasColumnType("text");
.HasColumnType("ntext");
b.Property<string>("ListFormCode")
.IsRequired()
@ -3341,7 +3340,7 @@ namespace Sozsoft.Platform.Migrations
.HasColumnType("nvarchar(20)");
b.Property<string>("MarginJson")
.HasColumnType("text");
.HasColumnType("ntext");
b.Property<string>("Name")
.HasMaxLength(128)
@ -3353,16 +3352,16 @@ namespace Sozsoft.Platform.Migrations
.HasDefaultValue(50);
b.Property<string>("PagerOptionJson")
.HasColumnType("text");
.HasColumnType("ntext");
b.Property<string>("PanesJson")
.HasColumnType("text");
.HasColumnType("ntext");
b.Property<string>("PermissionJson")
.HasColumnType("text");
.HasColumnType("ntext");
b.Property<string>("PivotOptionJson")
.HasColumnType("text");
.HasColumnType("ntext");
b.Property<string>("RoleId")
.HasMaxLength(256)
@ -3375,41 +3374,41 @@ namespace Sozsoft.Platform.Migrations
.HasColumnType("nvarchar(max)");
b.Property<string>("ScrollBarJson")
.HasColumnType("text");
.HasColumnType("ntext");
b.Property<string>("SearchPanelJson")
.HasColumnType("text");
.HasColumnType("ntext");
b.Property<string>("SelectCommand")
.HasColumnType("text");
.HasColumnType("ntext");
b.Property<int>("SelectCommandType")
.HasColumnType("int");
b.Property<string>("SelectFieldsDefaultValueJson")
.HasColumnType("text");
.HasColumnType("ntext");
b.Property<string>("SelectionJson")
.HasColumnType("text");
.HasColumnType("ntext");
b.Property<string>("SeriesJson")
.HasColumnType("text");
.HasColumnType("ntext");
b.Property<bool>("ShowNote")
.HasColumnType("bit");
b.Property<string>("SizeJson")
.HasColumnType("text");
.HasColumnType("ntext");
b.Property<string>("SortMode")
.HasMaxLength(20)
.HasColumnType("nvarchar(20)");
b.Property<string>("StateStoringJson")
.HasColumnType("text");
.HasColumnType("ntext");
b.Property<string>("SubFormsJson")
.HasColumnType("text");
.HasColumnType("ntext");
b.Property<string>("SubFormsListFormType")
.HasColumnType("nvarchar(max)");
@ -3423,13 +3422,13 @@ namespace Sozsoft.Platform.Migrations
.HasColumnType("nvarchar(256)");
b.Property<string>("TitleJson")
.HasColumnType("text");
.HasColumnType("ntext");
b.Property<string>("TodoOptionJson")
.HasColumnType("text");
.HasColumnType("ntext");
b.Property<string>("TooltipJson")
.HasColumnType("text");
.HasColumnType("ntext");
b.Property<string>("TreeOptionJson")
.HasColumnType("nvarchar(max)");
@ -3441,10 +3440,10 @@ namespace Sozsoft.Platform.Migrations
.HasColumnType("nvarchar(max)");
b.Property<string>("UpdateCommand")
.HasColumnType("text");
.HasColumnType("ntext");
b.Property<string>("UpdateFieldsDefaultValueJson")
.HasColumnType("text");
.HasColumnType("ntext");
b.Property<string>("UpdateServiceAddress")
.HasMaxLength(256)
@ -3455,19 +3454,19 @@ namespace Sozsoft.Platform.Migrations
.HasColumnType("nvarchar(256)");
b.Property<string>("ValueAxisJson")
.HasColumnType("text");
.HasColumnType("ntext");
b.Property<string>("WidgetsJson")
.HasColumnType("text");
.HasColumnType("ntext");
b.Property<int?>("Width")
.HasColumnType("int");
b.Property<string>("WorkflowJson")
.HasColumnType("text");
.HasColumnType("ntext");
b.Property<string>("ZoomAndPanJson")
.HasColumnType("text");
.HasColumnType("ntext");
b.HasKey("Id");
@ -3491,7 +3490,7 @@ namespace Sozsoft.Platform.Migrations
.HasColumnName("CreatorId");
b.Property<string>("CustomizationData")
.HasColumnType("text");
.HasColumnType("ntext");
b.Property<int>("CustomizationType")
.HasColumnType("int");
@ -3588,16 +3587,16 @@ namespace Sozsoft.Platform.Migrations
.HasColumnType("nvarchar(256)");
b.Property<string>("ColumnCustomizationJson")
.HasColumnType("text");
.HasColumnType("ntext");
b.Property<string>("ColumnFilterJson")
.HasColumnType("text");
.HasColumnType("ntext");
b.Property<string>("ColumnHeaderJson")
.HasColumnType("text");
.HasColumnType("ntext");
b.Property<string>("ColumnStylingJson")
.HasColumnType("text");
.HasColumnType("ntext");
b.Property<string>("CultureName")
.IsRequired()
@ -3611,10 +3610,10 @@ namespace Sozsoft.Platform.Migrations
.HasColumnType("int");
b.Property<string>("EditorOptions")
.HasColumnType("text");
.HasColumnType("ntext");
b.Property<string>("EditorScript")
.HasColumnType("text");
.HasColumnType("ntext");
b.Property<string>("EditorType2")
.HasMaxLength(64)
@ -3630,10 +3629,10 @@ namespace Sozsoft.Platform.Migrations
.HasColumnType("nvarchar(128)");
b.Property<string>("GroupSummaryJson")
.HasColumnType("text");
.HasColumnType("ntext");
b.Property<string>("GroupingJson")
.HasColumnType("text");
.HasColumnType("ntext");
b.Property<bool?>("IsActive")
.ValueGeneratedOnAdd()
@ -3641,7 +3640,7 @@ namespace Sozsoft.Platform.Migrations
.HasDefaultValue(true);
b.Property<string>("JoinTableJson")
.HasColumnType("text");
.HasColumnType("ntext");
b.Property<string>("ListFormCode")
.IsRequired()
@ -3654,13 +3653,13 @@ namespace Sozsoft.Platform.Migrations
.HasDefaultValue(30);
b.Property<string>("LookupJson")
.HasColumnType("text");
.HasColumnType("ntext");
b.Property<string>("PermissionJson")
.HasColumnType("text");
.HasColumnType("ntext");
b.Property<string>("PivotSettingsJson")
.HasColumnType("text");
.HasColumnType("ntext");
b.Property<string>("PlaceHolder")
.HasMaxLength(256)
@ -3681,14 +3680,14 @@ namespace Sozsoft.Platform.Migrations
.HasColumnType("int");
b.Property<string>("TotalSummaryJson")
.HasColumnType("text");
.HasColumnType("ntext");
b.Property<string>("UserId")
.HasMaxLength(256)
.HasColumnType("nvarchar(256)");
b.Property<string>("ValidationRuleJson")
.HasColumnType("text");
.HasColumnType("ntext");
b.Property<bool?>("Visible")
.ValueGeneratedOnAdd()
@ -3798,7 +3797,7 @@ namespace Sozsoft.Platform.Migrations
.HasColumnType("int");
b.Property<string>("ErrorsJson")
.HasColumnType("text");
.HasColumnType("ntext");
b.Property<int>("ExecRows")
.HasColumnType("int");
@ -3860,7 +3859,7 @@ namespace Sozsoft.Platform.Migrations
.HasColumnType("nvarchar(20)");
b.Property<string>("CompareOutcomesJson")
.HasColumnType("text");
.HasColumnType("ntext");
b.Property<decimal>("CompareValue")
.HasPrecision(18, 2)
@ -4092,7 +4091,7 @@ namespace Sozsoft.Platform.Migrations
b.Property<string>("ParticipantIdsJson")
.IsRequired()
.HasColumnType("text");
.HasColumnType("ntext");
b.Property<string>("ParticipantKey")
.IsRequired()
@ -4119,7 +4118,7 @@ namespace Sozsoft.Platform.Migrations
b.Property<string>("AttachmentsJson")
.IsRequired()
.HasColumnType("text");
.HasColumnType("ntext");
b.Property<Guid>("ConversationId")
.HasColumnType("uniqueidentifier");
@ -4156,7 +4155,7 @@ namespace Sozsoft.Platform.Migrations
b.Property<string>("RecipientIdsJson")
.IsRequired()
.HasColumnType("text");
.HasColumnType("ntext");
b.Property<Guid>("SenderId")
.HasColumnType("uniqueidentifier");

3
ui/.gitignore vendored
View file

@ -25,4 +25,5 @@ build
*.sln
*.sw?
.vite-cache/
src/devextreme-license.ts
src/devextreme-license.ts
src/components/visualDesigner/generated/componentProps.json

14
ui/package-lock.json generated
View file

@ -72,9 +72,7 @@
"@typescript-eslint/parser": "^8.62.1",
"@vitejs/plugin-react": "^4.2.1",
"autoprefixer": "^10.4.14",
"baseline-browser-mapping": "^2.9.19",
"browserslist": "^4.25.2",
"caniuse-lite": "^1.0.30001734",
"cssnano": "^6.0.1",
"eslint": "^9.39.0",
"eslint-config-prettier": "^10.1.8",
@ -5135,9 +5133,9 @@
"license": "MIT"
},
"node_modules/baseline-browser-mapping": {
"version": "2.10.0",
"resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.0.tgz",
"integrity": "sha512-lIyg0szRfYbiy67j9KN8IyeD7q7hcmqnJ1ddWmNt19ItGpNN64mnllmxUNFIOdOm6by97jlL6wfpTTJrmnjWAA==",
"version": "2.11.12",
"resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.11.12.tgz",
"integrity": "sha512-r7WnVImvVCeFpf2DOXfy41aPWzeNg3H/A2X4dKmy1QL0MSyyk/e7z8ihJ3N6Nn2PsdhkVlqnEfnUE4a05P2aTA==",
"dev": true,
"license": "Apache-2.0",
"bin": {
@ -5315,9 +5313,9 @@
}
},
"node_modules/caniuse-lite": {
"version": "1.0.30001774",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001774.tgz",
"integrity": "sha512-DDdwPGz99nmIEv216hKSgLD+D4ikHQHjBC/seF98N9CPqRX4M5mSxT9eTV6oyisnJcuzxtZy4n17yKKQYmYQOA==",
"version": "1.0.30001806",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001806.tgz",
"integrity": "sha512-72Cuvd95zbSYPKq6Fhg8eDJRlzgWDf7/mtoZv6Qe/DYNCEBdNxoA3+rZAU2ZhGCpZlns3EssFavaZomckT5Uuw==",
"dev": true,
"funding": [
{

View file

@ -5,9 +5,10 @@
"elstarVersion": "2.1.6",
"type": "module",
"scripts": {
"start": "vite",
"build": "node scripts/generate-version.js && vite build",
"build:production": "vite build",
"generate:component-metadata": "node scripts/generate-component-metadata.mjs",
"start": "node scripts/generate-component-metadata.mjs && vite",
"build": "node scripts/generate-component-metadata.mjs && node scripts/generate-version.js && vite build",
"build:production": "node scripts/generate-component-metadata.mjs && vite build",
"preview": "vite preview",
"lint": "eslint .",
"lint:fix": "npm run lint -- --fix",
@ -80,9 +81,7 @@
"@typescript-eslint/parser": "^8.62.1",
"@vitejs/plugin-react": "^4.2.1",
"autoprefixer": "^10.4.14",
"baseline-browser-mapping": "^2.9.19",
"browserslist": "^4.25.2",
"caniuse-lite": "^1.0.30001734",
"cssnano": "^6.0.1",
"eslint": "^9.39.0",
"eslint-config-prettier": "^10.1.8",

View file

@ -0,0 +1,182 @@
/* global console, process */
import fs from 'node:fs'
import path from 'node:path'
import prettier from 'prettier'
import ts from 'typescript'
import { fileURLToPath } from 'node:url'
const scriptDirectory = path.dirname(fileURLToPath(import.meta.url))
const uiRoot = path.resolve(scriptDirectory, '..')
const sourceRoot = path.join(uiRoot, 'src', 'components', 'ui')
const outputFile = path.join(sourceRoot, '..', 'visualDesigner', 'generated', 'componentProps.json')
const componentTypeOverrides = {
Calendar: 'CalenderProps',
ScrollBar: 'ScrollbarProps',
}
const namespaceOptions = {
'TypeAttributes.Size': ['lg', 'md', 'sm', 'xs'],
'TypeAttributes.ControlSize': ['lg', 'md', 'sm', 'xs'],
'TypeAttributes.Shape': ['round', 'circle', 'none'],
'TypeAttributes.Status': ['success', 'warning', 'danger', 'info'],
'TypeAttributes.FormLayout': ['horizontal', 'vertical', 'inline'],
'TypeAttributes.MenuVariant': ['light', 'dark', 'themed', 'transparent'],
'TypeAttributes.Direction': ['ltr', 'rtl'],
}
const walkFiles = (directory) =>
fs.readdirSync(directory, { withFileTypes: true }).flatMap((entry) => {
const target = path.join(directory, entry.name)
if (entry.isDirectory()) return walkFiles(target)
return /\.(ts|tsx)$/.test(entry.name) ? [target] : []
})
const sourceFiles = walkFiles(sourceRoot)
const metadataInputs = [...sourceFiles, fileURLToPath(import.meta.url)]
const outputIsCurrent =
!process.argv.includes('--force') &&
fs.existsSync(outputFile) &&
fs.statSync(outputFile).mtimeMs >=
Math.max(...metadataInputs.map((fileName) => fs.statSync(fileName).mtimeMs))
if (outputIsCurrent) {
console.log('Designer component metadata is up to date.')
process.exit(0)
}
const program = ts.createProgram(sourceFiles, {
target: ts.ScriptTarget.Latest,
jsx: ts.JsxEmit.ReactJSX,
})
const interfaces = new Map()
const defaultsByFile = new Map()
const literalValue = (node, sourceFile) => {
if (!node) return undefined
if (ts.isStringLiteral(node) || ts.isNoSubstitutionTemplateLiteral(node)) return node.text
if (ts.isNumericLiteral(node)) return Number(node.text)
if (node.kind === ts.SyntaxKind.TrueKeyword) return true
if (node.kind === ts.SyntaxKind.FalseKeyword) return false
if (node.kind === ts.SyntaxKind.NullKeyword) return null
if (ts.isArrayLiteralExpression(node)) {
const values = node.elements.map((element) => literalValue(element, sourceFile))
return values.some((value) => value === undefined) ? undefined : values
}
if (ts.isObjectLiteralExpression(node)) {
const result = {}
for (const property of node.properties) {
if (!ts.isPropertyAssignment(property)) return undefined
const name = property.name.getText(sourceFile).replace(/^['"]|['"]$/g, '')
const value = literalValue(property.initializer, sourceFile)
if (value === undefined) return undefined
result[name] = value
}
return result
}
return undefined
}
for (const sourceFile of program.getSourceFiles()) {
if (!path.resolve(sourceFile.fileName).startsWith(path.resolve(sourceRoot))) continue
const fileDefaults = {}
const visit = (node) => {
if (ts.isInterfaceDeclaration(node) && node.name.text.endsWith('Props')) {
interfaces.set(node.name.text, { node, sourceFile })
}
if (ts.isVariableDeclaration(node) && ts.isObjectBindingPattern(node.name)) {
for (const element of node.name.elements) {
if (!ts.isIdentifier(element.name) || !element.initializer) continue
const value = literalValue(element.initializer, sourceFile)
if (value !== undefined) fileDefaults[element.name.text] = value
}
}
ts.forEachChild(node, visit)
}
visit(sourceFile)
defaultsByFile.set(sourceFile.fileName, fileDefaults)
}
const indexSource = program.getSourceFile(path.join(sourceRoot, 'index.ts'))
if (!indexSource) throw new Error('UI component index could not be found.')
const componentNames = []
for (const statement of indexSource.statements) {
if (!ts.isExportDeclaration(statement) || statement.isTypeOnly || !statement.exportClause)
continue
if (!ts.isNamedExports(statement.exportClause)) continue
for (const element of statement.exportClause.elements) componentNames.push(element.name.text)
}
const unionOptions = (typeNode, sourceFile) => {
if (!typeNode) return []
const typeText = typeNode.getText(sourceFile)
if (namespaceOptions[typeText]) return namespaceOptions[typeText]
if (!ts.isUnionTypeNode(typeNode)) return []
return typeNode.types
.filter((type) => ts.isLiteralTypeNode(type) && ts.isStringLiteral(type.literal))
.map((type) => type.literal.text)
}
const propertyType = (typeNode, sourceFile) => {
const text = typeNode?.getText(sourceFile) || 'unknown'
const options = unionOptions(typeNode, sourceFile)
if (options.length) return { type: 'select', options }
if (text.includes('=>') || text.startsWith('MouseEventHandler')) return { type: 'function' }
if (/\bboolean\b/.test(text)) return { type: 'boolean' }
if (/\bnumber\b/.test(text)) return { type: 'number' }
if (/\[\]|Array<|ReadonlyArray</.test(text)) return { type: 'array' }
if (/CSSProperties|Record<|object/.test(text)) return { type: 'object' }
return { type: 'string' }
}
const commonProperties = [
{ name: 'className', tsType: 'string', type: 'string', required: false },
{ name: 'children', tsType: 'ReactNode', type: 'string', required: false },
{ name: 'style', tsType: 'CSSProperties', type: 'object', required: false },
]
const metadata = {}
for (const componentName of [...new Set(componentNames)]) {
const interfaceName = componentTypeOverrides[componentName] || `${componentName}Props`
const entry = interfaces.get(interfaceName)
if (!entry) continue
const { node, sourceFile } = entry
const defaults = defaultsByFile.get(sourceFile.fileName) || {}
const extendsCommonProps = node.heritageClauses?.some((clause) =>
clause.types.some((type) => type.expression.getText(sourceFile) === 'CommonProps'),
)
const properties = []
for (const member of node.members) {
if (!ts.isPropertySignature(member) || !member.name) continue
const name = member.name.getText(sourceFile).replace(/^['"]|['"]$/g, '')
const typeInfo = propertyType(member.type, sourceFile)
properties.push({
name,
tsType: member.type?.getText(sourceFile) || 'unknown',
...typeInfo,
required: !member.questionToken,
...(defaults[name] !== undefined ? { defaultValue: defaults[name] } : {}),
})
}
if (extendsCommonProps) {
for (const property of commonProperties) {
if (!properties.some((item) => item.name === property.name)) properties.push(property)
}
}
metadata[componentName] = {
interfaceName,
sourceFile: path.relative(uiRoot, sourceFile.fileName).replaceAll('\\', '/'),
properties,
}
}
fs.mkdirSync(path.dirname(outputFile), { recursive: true })
const formattedMetadata = await prettier.format(JSON.stringify(metadata), { parser: 'json' })
fs.writeFileSync(outputFile, formattedMetadata)
console.log(`Generated designer metadata for ${Object.keys(metadata).length} UI components.`)

View file

@ -25,6 +25,41 @@
}
}
&.select-md {
@apply text-sm;
.select-dropdown-indicator,
.select-clear-indicator {
@apply p-2 text-lg;
}
}
&.select-sm {
@apply text-sm;
.select-dropdown-indicator,
.select-clear-indicator {
@apply px-1.5 py-1 text-sm;
}
.select__multi-value {
@apply rounded text-sm;
}
}
&.select-xs {
@apply text-xs;
.select-dropdown-indicator,
.select-clear-indicator {
@apply px-1 py-0.5 text-xs;
}
.select__multi-value {
@apply rounded text-xs;
}
}
.select__single-value {
color: inherit;
}

View file

@ -1,783 +0,0 @@
import React, { useState, useEffect, useRef } from 'react'
import Editor from '@monaco-editor/react'
import { ComponentDefinition } from '../../proxy/developerKit/componentInfo'
import { generateSingleComponentJSX, generateUniqueId } from '@/utils/codeParser'
import { FaCheck, FaCode, FaMousePointer, FaSave, FaCog, FaTimes } from 'react-icons/fa'
import { Button, toast } from '../ui'
import Notification from '../ui/Notification/Notification'
interface ComponentCodeEditorProps {
code: string
onChange: (code: string) => void
onApplyCodeChanges: (code: string) => void
onResetCodeChanges: () => void
language?: string
theme?: 'vs-dark' | 'light'
onCursorChange?: (componentId: string | null) => void
onComponentAdded?: (componentDef: ComponentDefinition) => void
onComponentSave: () => void
}
export const ComponentCodeEditor: React.FC<ComponentCodeEditorProps> = ({
code,
onChange,
onApplyCodeChanges,
onResetCodeChanges,
language = 'typescript',
theme = 'vs-dark',
onCursorChange,
onComponentAdded,
onComponentSave,
}) => {
const [localCode, setLocalCode] = useState(code)
const [hasChanges, setHasChanges] = useState(false)
const [isFormatting, setIsFormatting] = useState(false)
const [showSettings, setShowSettings] = useState(false)
const [editorTheme, setEditorTheme] = useState(theme)
const [fontSize, setFontSize] = useState(14)
const [wordWrap, setWordWrap] = useState<'on' | 'off'>('on')
const [minimap, setMinimap] = useState(true)
const [showSuccessMessage, setShowSuccessMessage] = useState(false)
const [isDragOver, setIsDragOver] = useState(false)
const [dropIndicator, setDropIndicator] = useState<{
show: boolean
line: number
column: number
}>({ show: false, line: 0, column: 0 })
const editorRef = useRef<any>(null)
const containerRef = useRef<HTMLDivElement>(null)
// Refs to keep latest values accessible inside native DOM listeners
const localCodeRef = useRef(localCode)
const dropCallbacksRef = useRef({ onChange, onComponentAdded, onApplyCodeChanges })
useEffect(() => {
setLocalCode(code)
setHasChanges(false)
}, [code])
useEffect(() => {
localCodeRef.current = localCode
}, [localCode])
useEffect(() => {
dropCallbacksRef.current = { onChange, onComponentAdded, onApplyCodeChanges }
}, [onChange, onComponentAdded, onApplyCodeChanges])
const findComponentIdAtPosition = (
code: string,
position: { lineNumber: number; column: number },
): string | null => {
const lines = code.split('\n')
const currentLineIndex = position.lineNumber - 1
// Önce bulunduğu satırı kontrol et
const line = lines[currentLineIndex] || ''
const idMatch = line.match(/id=["']([\w-]+)["']/)
if (idMatch) return idMatch[1]
// Eğer bulunduğu satırda yoksa, yakındaki satırları kontrol et
// Önce yukarı doğru ara, sonra aşağı doğru ara
for (let distance = 1; distance <= 3; distance++) {
// Yukarı ara
if (currentLineIndex - distance >= 0) {
const upLine = lines[currentLineIndex - distance] || ''
// Component tag başlangıcını kontrol et
const componentMatch = upLine.match(/<([A-Z][a-zA-Z]*)/)
if (componentMatch) {
// Bu component tag'inin ID'sini ara (aynı satırda veya sonraki satırlarda)
for (
let j = currentLineIndex - distance;
j <= Math.min(lines.length - 1, currentLineIndex + 2);
j++
) {
const searchLine = lines[j] || ''
const foundId = searchLine.match(/id=["']([\w-]+)["']/)
if (foundId) return foundId[1]
}
}
}
// Aşağı ara
if (currentLineIndex + distance < lines.length) {
const downLine = lines[currentLineIndex + distance] || ''
const componentMatch = downLine.match(/<([A-Z][a-zA-Z]*)/)
if (componentMatch) {
// Bu component tag'inin ID'sini ara
for (
let j = currentLineIndex + distance;
j <= Math.min(lines.length - 1, currentLineIndex + distance + 2);
j++
) {
const searchLine = lines[j] || ''
const foundId = searchLine.match(/id=["']([\w-]+)["']/)
if (foundId) return foundId[1]
}
}
}
}
// Hiçbir şey bulunamadıysa, cursor'ın bulunduğu satırda herhangi bir JSX tag var mı kontrol et
const jsxMatch = line.match(/<\/?([A-Z][a-zA-Z]*)/)
if (jsxMatch) {
// Bu tag'e ait ID'yi bulabilir miyiz?
const componentName = jsxMatch[1]
for (
let i = Math.max(0, currentLineIndex - 5);
i <= Math.min(lines.length - 1, currentLineIndex + 5);
i++
) {
const searchLine = lines[i] || ''
if (searchLine.includes(`<${componentName}`) && searchLine.includes('id=')) {
const foundId = searchLine.match(/id=["']([\w-]+)["']/)
if (foundId) return foundId[1]
}
}
}
return null
}
/**
* Returns true if the given 1-based lineNumber is inside a `return (...)` block.
* Uses parenthesis depth counting to track entry and exit of the return expression.
*/
const isPositionInsideJSXReturn = (codeStr: string, lineNumber: number): boolean => {
const lines = codeStr.split('\n')
let insideReturn = false
let parenDepth = 0
for (let i = 0; i < lineNumber && i < lines.length; i++) {
const line = lines[i]
if (!insideReturn) {
if (/\breturn\s*\(/.test(line)) {
insideReturn = true
parenDepth = 0
const returnIdx = line.search(/\breturn/)
const fromReturn = line.slice(returnIdx)
for (const ch of fromReturn) {
if (ch === '(') parenDepth++
else if (ch === ')') {
parenDepth--
if (parenDepth <= 0) {
insideReturn = false
break
}
}
}
}
} else {
for (const ch of line) {
if (ch === '(') parenDepth++
else if (ch === ')') {
parenDepth--
if (parenDepth <= 0) {
insideReturn = false
break
}
}
}
}
}
return insideReturn
}
const handleEditorCursorChange = () => {
if (!editorRef.current) return
const position = editorRef.current.getPosition()
if (!position) return
const id = findComponentIdAtPosition(localCode, position)
if (onCursorChange) onCursorChange(id)
}
const handleEditorDidMount = (editor: any, monaco: any) => {
editorRef.current = editor
monaco.languages.typescript.typescriptDefaults.setCompilerOptions({
target: monaco.languages.typescript.ScriptTarget.Latest,
allowNonTsExtensions: true,
moduleResolution: monaco.languages.typescript.ModuleResolutionKind.NodeJs,
module: monaco.languages.typescript.ModuleKind.CommonJS,
noEmit: true,
esModuleInterop: true,
jsx: monaco.languages.typescript.JsxEmit.React,
reactNamespace: 'React',
allowJs: true,
typeRoots: ['node_modules/@types'],
})
editor.addCommand(monaco.KeyMod.CtrlCmd | monaco.KeyCode.KeyS, () => {
handleApplyChanges()
})
editor.addCommand(monaco.KeyMod.CtrlCmd | monaco.KeyMod.Shift | monaco.KeyCode.KeyF, () => {
handleFormatCode()
})
editor.onDidChangeCursorPosition(handleEditorCursorChange)
// Intercept Monaco's native drop so it never inserts raw JSON text.
// We handle the drop ourselves: insert JSX when inside return(), skip otherwise.
const domNode = editor.getDomNode()
if (domNode) {
domNode.addEventListener(
'drop',
(nativeEvent: DragEvent) => {
nativeEvent.preventDefault()
nativeEvent.stopPropagation()
nativeEvent.stopImmediatePropagation()
if (!nativeEvent.dataTransfer) return
// Read component definition from drag data
let componentDefData: string | undefined
for (const fmt of ['application/json', 'text/plain']) {
const d = nativeEvent.dataTransfer.getData(fmt)
if (d) {
componentDefData = d
break
}
}
if (!componentDefData) return
let componentDef: any
try {
componentDef = JSON.parse(componentDefData)
} catch {
return
}
if (!componentDef?.name) return
// Resolve the drop position inside Monaco
const dropPos = editor.getTargetAtClientPoint(nativeEvent.clientX, nativeEvent.clientY)
const position = dropPos?.position ?? editor.getPosition()
if (!position) return
// Only allow drop inside JSX return(...) block
if (!isPositionInsideJSXReturn(localCodeRef.current, position.lineNumber)) {
toast.push(
<Notification type="warning" duration={3000}>
Bileşen yalnızca <strong>return(...)</strong> bloğu içine eklenebilir.
</Notification>,
{ placement: 'bottom-end' },
)
return
}
const componentId = generateUniqueId()
const adaptedProps = {
...(componentDef.properties || []).reduce(
(acc: any, prop: any) => {
acc[prop.name] = {
type: prop.type,
value: prop.value,
...(prop.options ? { options: prop.options } : {}),
}
return acc
},
{} as Record<string, any>,
),
id: { type: 'string', value: componentId },
}
const componentJSX = generateSingleComponentJSX(componentDef.name, adaptedProps)
const componentDefWithId = {
...componentDef,
id: componentId,
properties: [
...(componentDef.properties || []).filter((p: any) => p.name !== 'id'),
{ name: 'id', type: 'string', value: componentId, category: 'properties' },
],
}
const lines = localCodeRef.current.split('\n')
const indent = (lines[position.lineNumber - 1] || '').match(/^(\s*)/)?.[1] ?? ' '
const formattedJSX = `${indent}${componentJSX}`
const newLines = [...lines]
newLines.splice(position.lineNumber, 0, formattedJSX)
const newCode = newLines.join('\n')
setLocalCode(newCode)
const {
onChange: onChg,
onComponentAdded: onAdded,
onApplyCodeChanges: onApply,
} = dropCallbacksRef.current
onChg(newCode)
if (onAdded) onAdded(componentDefWithId)
setTimeout(() => onApply(newCode), 100)
setIsDragOver(false)
setDropIndicator({ show: false, line: 0, column: 0 })
},
true, // capture phase — fires before Monaco's own listeners
)
}
}
const handleCodeChange = (value: string | undefined) => {
if (value !== undefined) {
setLocalCode(value)
setHasChanges(value !== code)
onChange(value)
}
}
const handleApplyChanges = () => {
onApplyCodeChanges(localCode)
setHasChanges(false)
setShowSuccessMessage(true)
handleFormatCode()
setTimeout(() => setShowSuccessMessage(false), 3000)
}
const handleFormatCode = async () => {
if (editorRef.current) {
setIsFormatting(true)
try {
await editorRef.current.getAction('editor.action.formatDocument').run()
} catch (error) {
console.error('Kod formatlama hatası:', error)
} finally {
setIsFormatting(false)
}
}
}
const handleResetChanges = () => {
setLocalCode(code)
setHasChanges(false)
onResetCodeChanges()
}
// Drag & Drop handlers for Code Editor
const handleDragOver = (e: React.DragEvent) => {
e.preventDefault()
e.stopPropagation()
if (!editorRef.current || !containerRef.current) return
try {
const hasComponentData = e.dataTransfer.types.some(
(type) => type === 'application/json' || type === 'text/plain' || type === 'text',
)
if (hasComponentData) {
e.dataTransfer.dropEffect = 'copy'
setIsDragOver(true)
// Get cursor position from mouse coordinates
const rect = containerRef.current.getBoundingClientRect()
const x = e.clientX - rect.left
const y = e.clientY - rect.top
// Convert to editor position
const position = editorRef.current.getTargetAtClientPoint(x, y)
if (position) {
setDropIndicator({
show: true,
line: position.position.lineNumber,
column: position.position.column,
})
}
}
} catch (err) {
console.warn('Error handling drag over:', err)
}
}
const handleDragLeave = (e: React.DragEvent) => {
e.preventDefault()
e.stopPropagation()
if (!containerRef.current) return
const rect = containerRef.current.getBoundingClientRect()
const x = e.clientX
const y = e.clientY
if (x <= rect.left || x >= rect.right || y <= rect.top || y >= rect.bottom) {
setIsDragOver(false)
setDropIndicator({ show: false, line: 0, column: 0 })
}
}
const handleDrop = (e: React.DragEvent) => {
e.preventDefault()
e.stopPropagation()
setIsDragOver(false)
setDropIndicator({ show: false, line: 0, column: 0 })
if (!editorRef.current || !containerRef.current) return
try {
let componentDefData
const formats = ['application/json', 'text/plain', 'text']
// Sürüklenen bileşenin verisini al
for (const format of formats) {
const data = e.dataTransfer.getData(format)
if (data) {
componentDefData = data
break
}
}
if (!componentDefData) return
let componentDef
try {
componentDef = JSON.parse(componentDefData)
} catch (error) {
console.error('Component verisi çözümlenemedi:', error)
return
}
// 1. Component ID'sini oluştur
const componentId = generateUniqueId()
// 2. Props'ları adapt et ve ID'yi dahil et
const adaptedProps = {
...(componentDef.properties || []).reduce(
(acc: any, prop: any) => {
acc[prop.name] = {
type: prop.type,
value: prop.value,
...(prop.options ? { options: prop.options } : {}),
}
return acc
},
{} as Record<string, any>,
),
id: {
type: 'string',
value: componentId,
},
}
// 3. JSX oluştur
const componentJSX = generateSingleComponentJSX(componentDef.name, adaptedProps)
// 4. Component tanımını ID ile genişlet
const componentDefWithId = {
...componentDef,
id: componentId,
properties: [
...(componentDef.properties || []).filter((p: any) => p.name !== 'id'),
{
name: 'id',
type: 'string',
value: componentId,
category: 'properties',
},
],
}
// 5. Pozisyonu al
let position = editorRef.current.getPosition()
if (!position) {
console.error('Geçersiz pozisyon tespit edildi. Yedek pozisyon kullanılıyor.')
position = { lineNumber: 0, column: 0 }
}
// 6. JSX kodunu pozisyonda ekle
const newCode = insertJSXAtPosition(localCode, componentJSX, position)
setLocalCode(newCode)
onChange(newCode)
// 7. Parent'a bildirim gönder
if (onComponentAdded) {
onComponentAdded(componentDefWithId)
}
// 8. Uygulamayı tetikle
setTimeout(() => {
onApplyCodeChanges(newCode)
}, 100)
} catch (error) {
console.error('Bileşen bırakılırken hata oluştu:', error)
}
}
const insertJSXAtPosition = (
code: string,
jsx: string,
position: { lineNumber: number; column: number },
): string => {
const updatedCode = code
const lines = updatedCode.split('\n')
// Pozisyonu doğrula (import eklendiyse satır numaraları değişmiş olabilir)
if (position.lineNumber < 1 || position.lineNumber > lines.length) {
console.error('Geçersiz satır numarası:', position.lineNumber)
position.lineNumber = lines.length // Geçersiz satırda son satıra ekle
}
const line = lines[position.lineNumber - 1] // 1 tabanlı indeks, 0 tabanlıya çevrilir
if (!line) {
console.error('Satır bulunamadı:', position.lineNumber)
return updatedCode // Eğer satır yoksa, kodu değiştirmeden döndür
}
// Satırın indentasyonunu belirle
const indentMatch = line.match(/^(\s*)/)
const indent = indentMatch ? indentMatch[1] : ' ' // Default 6 boşluk
// Component'i yeni satıra ekle (mevcut satırın altına)
const formattedJSX = `${indent}${jsx}`
lines.splice(position.lineNumber, 0, formattedJSX)
return lines.join('\n') // Tüm satırları birleştir ve yeni kodu döndür
}
const handleDragEnter = (e: React.DragEvent) => {
e.preventDefault()
e.stopPropagation()
}
return (
<div
ref={containerRef}
className={`w-full h-full flex flex-col min-h-0 max-h-full bg-gray-900 text-white relative ${
isDragOver ? 'ring-2 ring-blue-500 ring-offset-2' : ''
}`}
style={{ flex: 1, minHeight: 0, maxHeight: '100vh' }}
onDragOver={handleDragOver}
onDragEnter={handleDragEnter}
onDragLeave={handleDragLeave}
onDrop={handleDrop}
>
{/* Drop Indicator */}
{dropIndicator.show && (
<div className="absolute inset-0 bg-blue-500 bg-opacity-10 border-2 border-dashed border-blue-500 rounded-lg flex items-center justify-center z-50 pointer-events-none">
<div className="bg-blue-500 text-white px-4 py-2 rounded-lg font-medium shadow-lg">
Bileşeni Line {dropIndicator.line}, Column {dropIndicator.column} konumuna bırak
</div>
</div>
)}
{/* Header */}
<div className="bg-gray-800 border-b border-gray-700 p-4 flex items-end justify-between shrink-0">
<div className="flex items-center gap-2">
<Button
loading={isFormatting}
icon={<FaCode className="w-4 h-4" />}
variant="default"
size="sm"
onClick={handleFormatCode}
>
Formatla
</Button>
<Button
icon={<FaCog className="w-4 h-4" />}
variant={showSettings ? 'solid' : 'default'}
color="blue-600"
size="sm"
onClick={() => setShowSettings(!showSettings)}
>
Ayarlar
</Button>
<Button
icon={<FaTimes className="w-4 h-4" />}
variant="solid"
color="red-600"
size="sm"
onClick={handleResetChanges}
>
Sıfırla
</Button>
<Button
icon={<FaCheck className="w-4 h-4" />}
variant="solid"
color="green-600"
size="sm"
onClick={handleApplyChanges}
>
Uygula
</Button>
</div>
<div className="col-span-2 flex items-center justify-end">
<Button
icon={<FaSave className="w-4 h-4" />}
variant="solid"
color="yellow-600"
size="sm"
className="shadow-sm"
onClick={onComponentSave}
>
Kaydet
</Button>
</div>
</div>
{/* Settings Panel */}
{showSettings && (
<div className="bg-gray-800 border-b border-gray-700 p-4 shrink-0">
<div className="grid grid-cols-4 gap-4">
<div>
<label className="block text-sm font-medium text-gray-300 mb-2">Tema</label>
<select
value={editorTheme}
className="w-full px-3 py-2 bg-gray-700 border border-gray-600 rounded-md text-white text-sm focus:outline-none focus:ring-2 focus:ring-blue-500"
onChange={(e) => setEditorTheme(e.target.value as 'vs-dark' | 'light')}
>
<option value="vs-dark">Koyu</option>
<option value="light">ık</option>
</select>
</div>
<div>
<label className="block text-sm font-medium text-gray-300 mb-2">Font Boyutu</label>
<input
type="number"
min="10"
max="24"
value={fontSize}
className="w-full px-3 py-2 bg-gray-700 border border-gray-600 rounded-md text-white text-sm focus:outline-none focus:ring-2 focus:ring-blue-500"
onChange={(e) => setFontSize(parseInt(e.target.value))}
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-300 mb-2">
Kelime Kaydırma
</label>
<select
value={wordWrap}
className="w-full px-3 py-2 bg-gray-700 border border-gray-600 rounded-md text-white text-sm focus:outline-none focus:ring-2 focus:ring-blue-500"
onChange={(e) => setWordWrap(e.target.value as 'on' | 'off')}
>
<option value="on">ık</option>
<option value="off">Kapalı</option>
</select>
</div>
<div>
<label className="block text-sm font-medium text-gray-300 mb-2">Mini Harita</label>
<Button
block
variant={minimap ? 'solid' : 'default'}
color="blue-600"
size="sm"
onClick={() => setMinimap(!minimap)}
>
{minimap ? 'Etkin' : 'Devre Dışı'}
</Button>
</div>
</div>
</div>
)}
{/* Editor */}
<div className="flex-1 h-full">
<Editor
height="100%"
width="100%"
language={language}
theme={editorTheme}
value={localCode}
options={{
fontSize: fontSize,
wordWrap: wordWrap,
minimap: { enabled: minimap },
automaticLayout: true,
scrollBeyondLastLine: false,
renderWhitespace: 'selection',
bracketPairColorization: { enabled: true },
guides: {
bracketPairs: true,
indentation: true,
},
suggest: {
showKeywords: true,
showSnippets: true,
showFunctions: true,
showConstructors: true,
showFields: true,
showVariables: true,
showClasses: true,
showStructs: true,
showInterfaces: true,
showModules: true,
showProperties: true,
showEvents: true,
showOperators: true,
showUnits: true,
showValues: true,
showConstants: true,
showEnums: true,
showEnumMembers: true,
showColors: true,
showFiles: true,
showReferences: true,
showFolders: true,
showTypeParameters: true,
showUsers: true,
showIssues: true,
},
quickSuggestions: {
other: true,
comments: true,
strings: true,
},
parameterHints: {
enabled: true,
},
hover: {
enabled: true,
},
contextmenu: true,
mouseWheelZoom: true,
cursorBlinking: 'smooth',
cursorSmoothCaretAnimation: 'on',
smoothScrolling: true,
folding: true,
foldingStrategy: 'indentation',
showFoldingControls: 'always',
unfoldOnClickAfterEndOfLine: false,
tabSize: 2,
insertSpaces: true,
detectIndentation: true,
trimAutoWhitespace: true,
formatOnPaste: true,
formatOnType: true,
}}
onChange={handleCodeChange}
onMount={handleEditorDidMount}
/>
</div>
{/* Status Bar */}
<div className="bg-gray-800 border-t border-gray-700 px-4 py-2 flex items-center justify-between text-sm text-gray-400 shrink-0">
<div className="flex items-center gap-4">
<span>TypeScript React</span>
<span>UTF-8</span>
<span>LF</span>
</div>
<div className="flex items-center gap-4">
<span>Satır 1, Sütun 1</span>
<span>Boşluklar: 2</span>
{hasChanges && <span className="text-orange-400"> Kaydedilmemiş değişiklikler</span>}
{showSuccessMessage && (
<span className="text-green-400 flex items-center gap-1">
<FaCheck className="w-4 h-4" />
</span>
)}
{isDragOver && (
<span className="text-blue-400 flex items-center gap-1">
<FaMousePointer className="w-4 h-4" />
Bileşeni bırakmaya hazır
</span>
)}
</div>
</div>
</div>
)
}

View file

@ -1,190 +0,0 @@
import React, { useMemo, useState } from "react";
import { FaSearch, FaSquare } from 'react-icons/fa';
import { ComponentDefinition, HookInfo, PropertyInfo } from "../../proxy/developerKit/componentInfo";
import { getAllComponentDefinitions } from "./data/componentDefinitions";
import navigationIcon from "@/proxy/menus/navigation-icon.config";
interface ComponentLibraryProps {
onDragStart: (componentDef: ComponentDefinition, e: React.DragEvent) => void;
}
export const ComponentLibrary: React.FC<ComponentLibraryProps> = ({
onDragStart,
}) => {
const [searchTerm, setSearchTerm] = useState("");
const handleDragStart = (
componentDef: ComponentDefinition,
e: React.DragEvent
) => {
e.stopPropagation();
try {
const data = JSON.stringify(componentDef);
e.dataTransfer.setData("text/plain", data);
e.dataTransfer.setData("application/json", data);
} catch (error) {
console.error("Error setting drag data:", error);
}
e.dataTransfer.effectAllowed = "copy";
if (e.dataTransfer.setDragImage) {
try {
const dragPreview = document.createElement("div");
dragPreview.className = "component-drag-preview";
dragPreview.innerHTML = `<div class="flex items-center">
<span class="mr-2">${componentDef.icon || "📦"}</span>
<span>${componentDef.name}</span>
</div>`;
Object.assign(dragPreview.style, {
position: "absolute",
top: "-1000px",
left: "0",
padding: "8px 12px",
backgroundColor: "white",
border: "2px solid #4f46e5",
borderRadius: "4px",
boxShadow: "0 2px 5px rgba(0,0,0,0.2)",
zIndex: "9999",
pointerEvents: "none",
});
document.body.appendChild(dragPreview);
e.dataTransfer.setDragImage(dragPreview, 20, 20);
setTimeout(() => document.body.removeChild(dragPreview), 0);
} catch (error) {
console.error("Error setting drag image:", error);
}
}
onDragStart(componentDef, e);
};
const filteredComponents = useMemo(
() =>
getAllComponentDefinitions().filter(
(comp) =>
comp.name.toLowerCase().includes(searchTerm.toLowerCase()) ||
comp.description.toLowerCase().includes(searchTerm.toLowerCase())
),
[searchTerm]
);
const categories = [
"basic",
"form",
"layout",
"feedback",
"media",
"interactive",
"navigation",
"data",
]
.map((cat: any) => ({
id: cat,
name: cat,
components: filteredComponents?.filter((c) => c.category === cat),
}))
.filter((cat) => cat.components.length > 0);
const getIcon = (iconName: string): React.ComponentType<any> => {
return navigationIcon[iconName] || FaSquare;
};
return (
<div className="w-full bg-gray-900 text-white flex flex-col h-full">
{/* Arama kutusu */}
<div className="p-4 border-b border-gray-700">
<div className="relative">
<FaSearch className="absolute left-3 top-1/2 transform -translate-y-1/2 w-4 h-4 text-gray-400" />
<input
type="text"
placeholder="Search Components..."
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
className="w-full pl-10 pr-4 py-2 bg-gray-800 border border-gray-600 rounded-lg text-sm text-white placeholder-gray-400 focus:outline-none focus:ring-2 focus:ring-blue-500"
/>
</div>
</div>
{/* Bileşen kategorileri */}
<div className="flex-1 overflow-y-auto">
<div className="p-4 space-y-6">
{categories.map((category) => (
<div key={category.id}>
<h3 className="text-xs font-semibold text-gray-400 uppercase tracking-wider mb-3">
{category.name}
</h3>
<div className="space-y-1">
{category.components.map((componentDef) => {
const IconComponent = getIcon(componentDef.icon);
return (
<div
key={componentDef.name}
className="component-library-item flex items-center p-3 bg-gray-800 rounded-lg cursor-move hover:bg-gray-700 border border-gray-700 hover:border-gray-600"
draggable
onDragStart={(e) => handleDragStart(componentDef, e)}
onDragEnd={(e) => {
e.preventDefault();
e.stopPropagation();
}}
>
<div className="w-8 h-8 bg-gray-700 rounded-lg flex items-center justify-center mr-3 flex-shrink-0">
<IconComponent className="w-4 h-4 text-gray-300" />
</div>
<div className="flex-1 min-w-0 pointer-events-none">
<span className="text-sm font-medium text-gray-200 block truncate">
{componentDef.name}
</span>
<p className="text-xs text-gray-400 truncate">
{componentDef.description}
</p>
{/* Özellikler */}
{componentDef.properties?.length > 0 && (
<div className="mt-1 flex flex-wrap gap-1">
{componentDef.properties.slice(0, 2).map((prop: PropertyInfo) => (
<span
key={prop.name}
className="bg-gray-700 text-gray-300 text-[10px] px-2 py-0.5 rounded mr-1"
>
{prop.name}
</span>
))}
{componentDef.properties.length > 2 && (
<span className="text-gray-500 text-[10px]">
...
</span>
)}
</div>
)}
{/* Hooklar */}
{componentDef.hooks?.length > 0 && (
<div className="mt-1 flex flex-wrap gap-1">
{componentDef.hooks.slice(0, 2).map((hook: HookInfo) => (
<span
key={hook.name}
className="bg-gray-900 text-green-300 text-[10px] px-2 py-0.5 rounded mr-1"
>
{hook.name}
</span>
))}
{componentDef.hooks.length > 2 && (
<span className="text-gray-500 text-[10px]">
...
</span>
)}
</div>
)}
</div>
</div>
);
})}
</div>
</div>
))}
</div>
</div>
</div>
);
};

View file

@ -1,50 +0,0 @@
import React from 'react';
import { ComponentInfo } from '../../proxy/developerKit/componentInfo';
import { Button } from '../ui';
interface ComponentSelectorProps {
components: ComponentInfo[];
selectedComponentId: string | null;
onSelectComponent: (componentId: string | null) => void;
onRefresh: () => void;
}
const ComponentSelector: React.FC<ComponentSelectorProps> = ({
components,
selectedComponentId,
onSelectComponent,
onRefresh
}) => {
return (
<div className="p-4 bg-white dark:bg-gray-900 border-b border-gray-200 dark:border-gray-700">
<div className="flex items-center justify-between mb-2">
<label className="block text-sm font-medium text-gray-700 dark:text-gray-200">
Select Component
</label>
<Button
variant='solid'
size="sm"
onClick={onRefresh}
className="px-3 py-1 bg-blue-500 text-white text-xs rounded hover:bg-blue-600 dark:bg-blue-600 dark:hover:bg-blue-700 dark:text-white transition-colors"
title="Refresh component list"
>
Refresh
</Button>
</div>
<select
value={selectedComponentId || ''}
onChange={(e) => onSelectComponent(e.target.value || null)}
className="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 dark:bg-gray-800 dark:text-white dark:placeholder-gray-400"
>
<option value="">No component selected</option>
{components.map(component => (
<option key={component.id} value={component.id}>
{component.type} - {component.id}
</option>
))}
</select>
</div>
);
};
export default ComponentSelector;

View file

@ -1,79 +0,0 @@
import React from "react";
import {
FaCog,
FaTimes,
FaBars,
FaEye,
FaEyeSlash
} from 'react-icons/fa';
import { Button } from "../ui";
import { PanelState } from "./data/componentDefinitions";
interface PanelManagerProps {
isOpen: boolean;
onClose: () => void;
panelState: PanelState;
onPanelToggle: (panel: keyof PanelState) => void;
}
export const PanelManager: React.FC<PanelManagerProps> = ({
isOpen,
onClose,
panelState,
onPanelToggle,
}) => {
if (!isOpen) return null;
const paneller = [
{ key: "toolbox" as keyof PanelState, label: "Toolbox", icon: FaBars },
{ key: "properties" as keyof PanelState, label: "Properties", icon: FaCog },
];
return (
<div className="fixed inset-0 bg-black bg-opacity-50 z-50 flex items-center justify-center">
<div className="bg-white dark:bg-gray-900 rounded-lg shadow-xl w-96 max-w-full mx-4">
<div className="flex items-center justify-between p-4 border-b border-gray-200 dark:border-gray-700">
<div className="flex items-center space-x-2">
<FaBars className="w-5 h-5 text-blue-600 dark:text-blue-400" />
<h2 className="text-base font-semibold text-gray-900 dark:text-gray-100">Panel Manager</h2>
</div>
<Button
onClick={onClose}
icon={<FaTimes className="w-5 h-5" />}
variant="plain"
size="xs"
title="Kapat"
/>
</div>
<div className="p-4">
<p className="text-sm text-gray-600 dark:text-gray-300 mb-4">Customize Workspace</p>
<div className="space-y-3">
<h3 className="text-sm font-medium text-gray-900 dark:text-gray-100">Panels</h3>
{paneller.map(({ key, label, icon: Icon }) => (
<div key={key} className="flex items-center justify-between p-3 bg-gray-50 dark:bg-gray-800 rounded-lg">
<div className="flex items-center space-x-3">
<Icon className="w-4 h-4 text-gray-600 dark:text-gray-300" />
<span className="text-sm font-medium text-gray-700 dark:text-gray-200">{label}</span>
</div>
<Button
onClick={() => onPanelToggle(key)}
icon={
panelState[key] ? (
<FaEye className="w-4 h-4" />
) : (
<FaEyeSlash className="w-4 h-4" />
)
}
variant={panelState[key] ? "twoTone" : "plain"}
size="xs"
title={panelState[key] ? "Hide" : "Show"}
/>
</div>
))}
</div>
</div>
</div>
</div>
);
};

View file

@ -1,619 +0,0 @@
import React, { useState, useEffect } from "react";
import TailwindModal from "./TailwindModal";
import { ComponentInfo, HookInfo, PropertyInfo } from "../../proxy/developerKit/componentInfo";
import { getComponentDefinition } from "./data/componentDefinitions";
import { Button } from "../ui";
import { FaCheck, FaCode, FaTimes, FaTrash } from "react-icons/fa";
interface PropertyPanelProps {
selectedComponent: ComponentInfo | null;
currentCode: string;
onPropertiesChange: (
componentId: string,
updates: Record<string, any>
) => void;
onHookToggle: (
componentId: string,
hookType: string,
enabled: boolean
) => void;
onMultipleHookToggle: (
toggles: { componentId: string; hookType: string; enabled: boolean }[]
) => void;
onDeleteComponent: (componentId: string) => void;
}
const PropertyPanel: React.FC<PropertyPanelProps> = ({
selectedComponent,
currentCode,
onPropertiesChange,
onHookToggle,
onMultipleHookToggle,
onDeleteComponent,
}) => {
const [tailwindModalOpen, setTailwindModalOpen] = useState(false);
const [currentTailwindProperty, setCurrentTailwindProperty] =
useState<string>("");
const [activeHooks, setActiveHooks] = useState<Set<string>>(new Set());
const [activeTab, setActiveTab] = useState<"props" | "hooks">("props");
// Local state for pending changes
const [pendingProperties, setPendingProperties] = useState<
Record<string, any>
>({});
const [pendingEvents, setPendingEvents] = useState<Record<string, string>>(
{}
);
const [pendingHooks, setPendingHooks] = useState<Record<string, boolean>>({});
const [hasChanges, setHasChanges] = useState(false);
const [hasHookChanges, setHasHookChanges] = useState(false);
const componentDefinition = selectedComponent
? getComponentDefinition(selectedComponent.name)
: null;
// Reset pending changes when component changes
useEffect(() => {
setPendingProperties({});
setPendingEvents({});
setPendingHooks({});
setHasChanges(false);
setHasHookChanges(false);
}, [selectedComponent?.id]);
// Check which hooks are currently active in the code
useEffect(() => {
if (selectedComponent && currentCode) {
const hooks = new Set<string>();
// Check for useState
if (
currentCode.includes(
`const [state_${selectedComponent.id}, setState_${selectedComponent.id}]`
)
) {
hooks.add("useState");
}
// Check for useRef
if (
currentCode.includes(`const ref_${selectedComponent.id}`) &&
currentCode.includes(`ref={ref_${selectedComponent.id}`)
) {
hooks.add("useRef");
}
// // Check for useEffect
// if (
// currentCode.includes("useEffect") &&
// currentCode.includes(selectedComponent.id)
// ) {
// hooks.add("useEffect");
// }
setActiveHooks(hooks);
}
}, [selectedComponent, currentCode]);
// Handle local property changes
const handleLocalPropertyChange = (propName: string, value: any) => {
setPendingProperties((prev) => ({
...prev,
[propName]: value,
}));
setHasChanges(true);
};
// Handle local hook changes
const handleLocalHookToggle = (hookType: string, enabled: boolean) => {
setPendingHooks((prev) => ({
...prev,
[hookType]: enabled,
}));
setHasHookChanges(true);
};
// Apply only property/event changes
const handleApplyPropChanges = () => {
if (!selectedComponent) return;
// Combine all changes into a single update object
const allUpdates = {
...pendingProperties,
...pendingEvents,
};
// Apply property and event changes together
if (Object.keys(allUpdates).length > 0) {
onPropertiesChange(selectedComponent.id, allUpdates);
}
// Reset pending changes
setPendingProperties({});
setPendingEvents({});
setHasChanges(false);
};
// Apply only hook changes
const handleApplyHookChanges = () => {
if (!selectedComponent) return;
const hookToggles = Object.entries(pendingHooks).map(
([hookType, enabled]) => ({
componentId: selectedComponent.id,
hookType,
enabled,
})
);
if (hookToggles.length > 1) {
onMultipleHookToggle(hookToggles);
} else if (hookToggles.length === 1) {
const { componentId, hookType, enabled } = hookToggles[0];
onHookToggle(componentId, hookType, enabled);
}
// Reset pending changes
setPendingHooks({});
setHasHookChanges(false);
};
// Reset all pending changes
const handleResetChanges = () => {
setPendingProperties({});
setPendingEvents({});
setPendingHooks({});
setHasChanges(false);
setHasHookChanges(false);
};
const openTailwindModal = (propertyName: string) => {
setCurrentTailwindProperty(propertyName);
setTailwindModalOpen(true);
};
const handleTailwindClassSelect = (className: string) => {
const currentValue =
pendingProperties[currentTailwindProperty] ||
selectedComponent?.props[currentTailwindProperty] ||
"";
const newValue = currentValue ? `${currentValue} ${className}` : className;
handleLocalPropertyChange(currentTailwindProperty, newValue);
// Don't close modal - let user continue selecting
};
const renderPropertyControl = (property: PropertyInfo) => {
// Handle children property specially - get from children, not props
const currentValue =
property.name === "children"
? typeof selectedComponent?.children === "string"
? selectedComponent.children
: selectedComponent?.props[property.name] || property.value
: selectedComponent?.props[property.name] || property.value;
// Use pending value if available, otherwise use current value
const value =
pendingProperties[property.name] !== undefined
? pendingProperties[property.name]
: currentValue;
const isTailwindProperty = ["className", "class", "css"].includes(
property.name
);
const isColorProperty = property.name.toLowerCase().includes("color");
// Don't show children property if component has nested elements
if (
property.name === "children" &&
Array.isArray(selectedComponent?.children)
) {
return null;
}
let arrayError = "";
let arrayInputValue = "";
if (property.type === "array") {
try {
arrayInputValue = JSON.stringify(value, null, 2);
} catch {
arrayInputValue = "";
arrayError = "Array verisi gösterilemiyor.";
}
}
return (
<div key={property.name} className="mb-4">
<label className="block text-sm font-medium text-gray-700 dark:text-gray-200 mb-2">
{property.name}
{property.description && (
<span className="text-gray-500 dark:text-gray-400 text-xs ml-1">
({property.description})
</span>
)}
{pendingProperties[property.name] !== undefined && (
<span className="text-orange-500 text-xs ml-1">(değiştirildi)</span>
)}
</label>
<div className="flex gap-2 flex-col">
{property.type === "boolean" && (
<label className="flex items-center">
<input
type="checkbox"
checked={Boolean(value)}
onChange={(e) =>
handleLocalPropertyChange(property.name, e.target.checked)
}
className="mr-2"
/>
<span className="text-sm text-gray-600 dark:text-gray-300">{property.name}</span>
</label>
)}
{property.type === "select" && property.options && (
<select
value={value || ""}
onChange={(e) =>
handleLocalPropertyChange(property.name, e.target.value)
}
className="flex-1 px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 dark:bg-gray-800 dark:text-white dark:placeholder-gray-400"
>
<option value="">Select {property.name}</option>
{property.options.map((option) => (
<option key={option} value={option}>
{option}
</option>
))}
</select>
)}
{property.type === "function" && (
<>
<input
type="text"
value={value || ""}
onChange={(e) =>
handleLocalPropertyChange(property.name, e.target.value)
}
className="flex-1 px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 dark:bg-gray-800 dark:text-white dark:placeholder-gray-400"
placeholder={`Enter ${property.name}`}
/>
{isTailwindProperty && (
<Button
onClick={() => openTailwindModal(property.name)}
icon={<FaCode className="w-3 h-3" />}
variant="solid"
size="xs"
title="Select Tailwind Classes"
>
TW
</Button>
)}
{isColorProperty && (
<input
type="color"
value={value || "#000000"}
onChange={(e) =>
handleLocalPropertyChange(property.name, e.target.value)
}
className="w-10 h-10 border border-gray-300 dark:border-gray-600 rounded-md bg-white dark:bg-gray-800"
/>
)}
</>
)}
{property.type === "string" && (
<>
<input
type="text"
value={value || ""}
onChange={(e) =>
handleLocalPropertyChange(property.name, e.target.value)
}
className="flex-1 px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 dark:bg-gray-800 dark:text-white dark:placeholder-gray-400"
placeholder={`Enter ${property.name}`}
/>
{isTailwindProperty && (
<Button
onClick={() => openTailwindModal(property.name)}
icon={<FaCode className="w-3 h-3" />}
variant="solid"
size="xs"
title="Select Tailwind Classes"
>
TW
</Button>
)}
{isColorProperty && (
<input
type="color"
value={value || "#000000"}
onChange={(e) =>
handleLocalPropertyChange(property.name, e.target.value)
}
className="w-10 h-10 border border-gray-300 dark:border-gray-600 rounded-md bg-white dark:bg-gray-800"
/>
)}
</>
)}
{property.type === "number" && (
<input
type="number"
value={value || 0}
onChange={(e) =>
handleLocalPropertyChange(property.name, Number(e.target.value))
}
className="flex-1 px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 dark:bg-gray-800 dark:text-white dark:placeholder-gray-400"
/>
)}
{property.type === "array" && (
<>
<textarea
className="flex-1 px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md font-mono text-xs focus:outline-none focus:ring-2 focus:ring-blue-500 dark:bg-gray-800 dark:text-white dark:placeholder-gray-400"
rows={Math.max(3, arrayInputValue.split('\n').length)}
value={arrayInputValue}
onChange={(e) => {
try {
const parsed = JSON.parse(e.target.value);
handleLocalPropertyChange(property.name, parsed);
arrayError = "";
} catch {
arrayError = "Geçersiz JSON formatı";
}
}}
placeholder="[\n { ... }\n]"
spellCheck={false}
/>
{arrayError && (
<span className="text-xs text-red-500">{arrayError}</span>
)}
</>
)}
</div>
</div>
);
};
const renderHookControl = (hook: HookInfo) => {
const currentlyActive = activeHooks.has(hook.type);
const isActive =
pendingHooks[hook.type] !== undefined
? pendingHooks[hook.type]
: currentlyActive;
return (
<div key={hook.name} className="mb-4">
<label className="flex items-center">
<input
type="checkbox"
checked={isActive}
onChange={(e) => handleLocalHookToggle(hook.type, e.target.checked)}
className="mr-2"
/>
<span className="text-sm font-medium text-gray-700">
{hook.name} ({hook.type})
</span>
{pendingHooks[hook.type] !== undefined && (
<span className="text-orange-500 text-xs ml-1">(değiştirildi)</span>
)}
</label>
</div>
);
};
if (!selectedComponent) {
return (
<div className="h-full bg-gray-50 dark:bg-gray-900 p-4">
<div className="text-center text-gray-500 dark:text-gray-400 mt-8">
<div className="text-4xl mb-4">🎯</div>
<h3 className="text-lg font-medium mb-2 text-gray-700 dark:text-gray-200">No Component Selected</h3>
<p className="text-sm text-gray-500 dark:text-gray-400">
Select a component from the editor to edit its properties
</p>
</div>
</div>
);
}
function getDynamicProperties(): PropertyInfo[] {
if (!selectedComponent) return [];
const allDefinitionProps = componentDefinition?.properties || [];
// Sadece tanımdan gelen properties
const defProps = allDefinitionProps.filter(
(p: any) => p.category === "properties"
);
// allDefinitionNames prop isimlerini belirle
const allDefinitionNames = new Set(allDefinitionProps.map((p:any) => p.name));
// Koddan gelen tüm props (id hariç), styling hariç, events hariç
const codeProps = Object.entries(selectedComponent.props || {})
.filter(([name]) => name !== "id" && !allDefinitionNames.has(name))
.map(([name, value]) => {
let type: PropertyInfo["type"] = "string";
if (typeof value === "boolean") type = "boolean";
else if (typeof value === "number") type = "number";
else if (typeof value === "function") type = "function";
else if (Array.isArray(value)) type = "array";
else if (typeof value === "object" && value !== null) type = "object";
return {
name,
type,
value,
category: "properties",
} as PropertyInfo;
});
// Merge - öncelik kodda olanlar
const merged: PropertyInfo[] = [];
const seen = new Set<string>();
for (const prop of codeProps) {
merged.push(prop);
seen.add(prop.name);
}
for (const prop of defProps) {
if (!seen.has(prop.name)) {
merged.push(prop);
}
}
return merged;
}
const properties = getDynamicProperties();
const hooks = componentDefinition?.hooks || [];
const styling =
componentDefinition?.properties?.filter((p: any) => p.category === "styling") ||
[];
const events =
componentDefinition?.properties?.filter((p: any) => p.category === "events") ||
[];
return (
<div className="w-full text-white flex flex-col h-full">
{/* Header */}
<div className="border-b bg-gray-50 dark:bg-gray-900 flex items-center justify-between dark:border-gray-700">
<div>
{(hasChanges || hasHookChanges) && (
<p className="text-sm text-orange-600 mt-1">
Bekleyen değişiklikler var
</p>
)}
{/* Tabs */}
<div className="flex gap-2 p-1">
<Button
variant={activeTab === "props" ? "twoTone" : "plain"}
size="xs"
onClick={() => setActiveTab("props")}
>
Properties
</Button>
<Button
variant={activeTab === "hooks" ? "twoTone" : "plain"}
size="xs"
onClick={() => setActiveTab("hooks")}
>
Hooks
</Button>
</div>
</div>
{/* Sil Butonu */}
<Button
variant="solid"
color="red-600"
size="xs"
icon={<FaTrash className="w-4 h-4" />}
className="mr-2"
onClick={() => {
if (selectedComponent) {
if (
window.confirm(
"Seçili komponenti silmek istediğinize emin misiniz?"
)
) {
onDeleteComponent(selectedComponent.id);
}
}
}}
title="Komponenti Sil"
>
Sil
</Button>
</div>
{/* Footer Action Buttons - her iki tabda da sabit */}
<div className="p-4 border-t">
<div className="flex gap-2">
<Button
size="sm"
variant="solid"
color="green-600"
icon={<FaCheck className="w-4 h-4" />}
onClick={
activeTab === "props"
? handleApplyPropChanges
: handleApplyHookChanges
}
disabled={activeTab === "props" ? !hasChanges : !hasHookChanges}
className="flex-1"
>
Uygula
</Button>
<Button
size="sm"
variant="solid"
color="red-600"
icon={<FaTimes className="w-4 h-4" />}
onClick={
activeTab === "props"
? handleResetChanges
: () => {
setPendingHooks({});
setHasHookChanges(false);
}
}
disabled={activeTab === "props" ? !hasChanges : !hasHookChanges}
className="flex-1"
>
İptal
</Button>
</div>
</div>
{/* Content */}
{activeTab === "props" && (
<div className="flex-1 text-black dark:text-gray-200 overflow-y-auto p-4 max-h-[calc(100vh-200px)] bg-white dark:bg-gray-900">
<h3 className="text-md font-medium text-gray-800 dark:text-gray-100 mb-4">Properties</h3>
{/* Properties */}
{properties.length > 0 && (
<div>{properties.map(renderPropertyControl)}</div>
)}
{/* Events */}
{events.length > 0 && (
<div>
<h3 className="text-md font-medium text-gray-800 dark:text-gray-100 mb-4 mt-6">
Events
</h3>
{events.map(renderPropertyControl)}
</div>
)}
{/* Styling */}
{styling.length > 0 && (
<div>
<h3 className="text-md font-medium text-gray-800 dark:text-gray-100 mb-4 mt-6">
Styling
</h3>
{styling.map(renderPropertyControl)}
</div>
)}
</div>
)}
{activeTab === "hooks" && (
<div className="flex-1 overflow-y-auto p-4">
{/* Sadece useState ve useRef göster */}
{hooks
.filter((h: any) => h.type === "useState" || h.type === "useRef")
.map(renderHookControl)}
</div>
)}
{/* Tailwind Modal */}
<TailwindModal
isOpen={tailwindModalOpen}
onClose={() => setTailwindModalOpen(false)}
onSelectClass={handleTailwindClassSelect}
currentValue={
pendingProperties[currentTailwindProperty] ||
(selectedComponent &&
selectedComponent.props[currentTailwindProperty]) ||
""
}
/>
</div>
);
};
export default PropertyPanel;

View file

@ -1,150 +1,79 @@
import React, { useState, useEffect } from 'react';
import { searchTailwindClasses, TAILWIND_CLASSES } from './data/tailwindClasses';
import { Button } from '../ui';
import { FaTimes } from 'react-icons/fa';
import React, { useMemo, useState } from 'react'
import { FaSearch, FaTimes } from 'react-icons/fa'
import { Button } from '@/components/ui'
interface TailwindModalProps {
isOpen: boolean;
onClose: () => void;
onSelectClass: (className: string) => void;
currentValue?: string;
isOpen: boolean
currentValue?: string
onClose: () => void
onSelectClass: (className: string) => void
}
const CLASS_GROUPS = {
Layout: ['block', 'inline-block', 'flex', 'inline-flex', 'grid', 'hidden', 'relative', 'absolute', 'fixed', 'sticky', 'overflow-auto', 'overflow-hidden'],
Flex: ['flex-row', 'flex-col', 'flex-wrap', 'items-start', 'items-center', 'items-end', 'items-stretch', 'justify-start', 'justify-center', 'justify-between', 'justify-end', 'gap-1', 'gap-2', 'gap-3', 'gap-4', 'gap-6', 'gap-8'],
Grid: ['grid-cols-1', 'grid-cols-2', 'grid-cols-3', 'grid-cols-4', 'grid-cols-6', 'grid-cols-12', 'col-span-1', 'col-span-2', 'col-span-3', 'col-span-6', 'col-span-12'],
Spacing: ['m-0', 'm-1', 'm-2', 'm-3', 'm-4', 'm-auto', 'mx-auto', 'my-2', 'my-4', 'p-0', 'p-1', 'p-2', 'p-3', 'p-4', 'p-6', 'p-8', 'px-2', 'px-3', 'px-4', 'px-6', 'py-1', 'py-2', 'py-3', 'py-4'],
Sizing: ['w-auto', 'w-full', 'w-1/2', 'w-1/3', 'w-2/3', 'h-auto', 'h-full', 'min-w-0', 'min-h-0', 'max-w-sm', 'max-w-md', 'max-w-lg', 'max-w-xl', 'max-w-full'],
Typography: ['text-xs', 'text-sm', 'text-base', 'text-lg', 'text-xl', 'text-2xl', 'font-normal', 'font-medium', 'font-semibold', 'font-bold', 'text-left', 'text-center', 'text-right', 'truncate', 'whitespace-nowrap'],
Colors: ['text-white', 'text-black', 'text-slate-500', 'text-slate-700', 'text-slate-900', 'bg-white', 'bg-slate-50', 'bg-slate-100', 'bg-slate-800', 'bg-slate-900', 'bg-sky-500', 'bg-blue-600', 'bg-red-500', 'bg-emerald-500'],
Border: ['border', 'border-0', 'border-2', 'border-slate-200', 'border-slate-700', 'rounded', 'rounded-md', 'rounded-lg', 'rounded-xl', 'rounded-full', 'shadow-sm', 'shadow', 'shadow-lg'],
State: ['cursor-pointer', 'select-none', 'transition', 'hover:opacity-80', 'hover:bg-slate-100', 'dark:bg-slate-900', 'dark:text-white', 'dark:border-slate-700'],
} as const
const TailwindModal: React.FC<TailwindModalProps> = ({
isOpen,
currentValue = '',
onClose,
onSelectClass,
currentValue = ''
}) => {
const [searchTerm, setSearchTerm] = useState('');
const [selectedCategory, setSelectedCategory] = useState('All');
const [filteredClasses, setFilteredClasses] = useState<string[]>([]);
const [search, setSearch] = useState('')
const [category, setCategory] = useState<keyof typeof CLASS_GROUPS | 'Tümü'>('Tümü')
const selectedClasses = useMemo(() => new Set(currentValue.split(/\s+/).filter(Boolean)), [currentValue])
const classes = useMemo(() => {
const source = category === 'Tümü' ? Object.values(CLASS_GROUPS).flat() : CLASS_GROUPS[category]
const query = search.trim().toLowerCase()
return [...new Set(source)].filter((className) => !query || className.toLowerCase().includes(query))
}, [category, search])
useEffect(() => {
if (searchTerm) {
const searchResults = searchTailwindClasses(searchTerm);
// Remove duplicates using Set
setFilteredClasses([...new Set(searchResults)]);
} else if (selectedCategory === 'All') {
const allClasses = TAILWIND_CLASSES.flatMap(cat => cat.classes);
// Remove duplicates using Set
setFilteredClasses([...new Set(allClasses)]);
} else {
const category = TAILWIND_CLASSES.find(cat => cat.category === selectedCategory);
setFilteredClasses(category ? [...new Set(category.classes)] : []);
}
}, [searchTerm, selectedCategory]);
const handleClassSelect = (className: string) => {
onSelectClass(className);
// Don't close modal - let user select multiple classes
};
if (!isOpen) return null;
if (!isOpen) return null
return (
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50">
<div className="bg-white rounded-lg shadow-2xl w-4/5 h-4/5 max-w-4xl max-h-4xl flex flex-col">
{/* Header */}
<div className="flex items-center justify-between p-4 border-b">
<h2 className="text-xl font-semibold text-gray-800">Tailwind CSS Classes</h2>
<Button
onClick={onClose}
icon={<FaTimes className="w-5 h-5" />}
variant="plain"
size="xs"
title="Close"
/>
<div className="fixed inset-0 z-[100] flex items-center justify-center bg-black/60 p-4" onMouseDown={onClose}>
<div className="flex max-h-[85vh] w-full max-w-4xl flex-col overflow-hidden rounded-xl bg-white shadow-2xl dark:bg-slate-900" onMouseDown={(event) => event.stopPropagation()}>
<div className="flex items-center justify-between border-b border-slate-200 p-4 dark:border-slate-700">
<div>
<h2 className="font-semibold text-slate-900 dark:text-white">Tailwind CSS sınıfları</h2>
<p className="mt-1 text-xs text-slate-500">Birden fazla sınıf seçebilirsiniz.</p>
</div>
<Button icon={<FaTimes />} size="xs" variant="plain" onClick={onClose} />
</div>
{/* Search and Filter */}
<div className="p-4 border-b bg-gray-50 text-black">
<div className="flex gap-4 mb-4">
<input
type="text"
placeholder="Search classes..."
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
className="flex-1 px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
/>
<select
value={selectedCategory}
onChange={(e) => setSelectedCategory(e.target.value)}
className="px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
>
<option value="All">All Categories</option>
{TAILWIND_CLASSES.map(category => (
<option key={category.category} value={category.category}>
{category.category}
</option>
))}
</select>
</div>
{/* Current Value Display */}
{currentValue && (
<div className="mb-4">
<label className="block text-sm font-medium text-gray-700 mb-2">
Current Value:
</label>
<div className="bg-gray-100 p-2 rounded border text-sm font-mono">
{currentValue}
</div>
</div>
)}
<div className="flex gap-3 border-b border-slate-200 p-4 dark:border-slate-700">
<label className="relative flex-1">
<FaSearch className="absolute left-3 top-3 text-slate-400" />
<input className="w-full rounded-md border border-slate-300 bg-white py-2 pl-9 pr-3 text-sm dark:border-slate-700 dark:bg-slate-950 dark:text-white" placeholder="Sınıf ara…" value={search} onChange={(event) => setSearch(event.target.value)} />
</label>
<select className="rounded-md border border-slate-300 bg-white px-3 text-sm dark:border-slate-700 dark:bg-slate-950 dark:text-white" value={category} onChange={(event) => setCategory(event.target.value as typeof category)}>
<option value="Tümü">Tüm kategoriler</option>
{Object.keys(CLASS_GROUPS).map((name) => <option key={name} value={name}>{name}</option>)}
</select>
</div>
{/* Class List */}
<div className="flex-1 overflow-hidden">
<div className="h-full overflow-y-auto p-4">
<div className="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-2">
{filteredClasses.map((className, index) => (
<div
key={`${className}-${index}`}
className="group relative"
>
<Button
block
variant="solid"
size="xs"
onClick={() => handleClassSelect(className)}
className="justify-start"
>
<span className="font-mono text-xs text-gray-600">
{className}
</span>
</Button>
</div>
))}
</div>
</div>
<div className="border-b border-slate-200 bg-slate-50 px-4 py-3 text-xs dark:border-slate-700 dark:bg-slate-950">
<span className="font-semibold text-slate-600 dark:text-slate-300">className: </span>
<code className="break-all text-sky-600 dark:text-sky-400">{currentValue || '—'}</code>
</div>
{/* Footer */}
<div className="p-4 border-t bg-gray-50 flex justify-between">
<div className="text-sm text-gray-600">
{filteredClasses.length} classes found
</div>
<div className="flex gap-2">
<Button
variant="default"
size="sm"
onClick={() => onSelectClass('')}
>
Clear
</Button>
<Button
variant="solid"
size="sm"
onClick={onClose}
>
Close
</Button>
</div>
<div className="grid flex-1 grid-cols-2 gap-2 overflow-y-auto p-4 sm:grid-cols-3 lg:grid-cols-4">
{classes.map((className) => (
<button key={className} className={`rounded-md border px-3 py-2 text-left font-mono text-xs transition ${selectedClasses.has(className) ? 'border-sky-500 bg-sky-50 text-sky-700 dark:bg-sky-950 dark:text-sky-300' : 'border-slate-200 text-slate-600 hover:border-sky-300 dark:border-slate-700 dark:text-slate-300'}`} type="button" onClick={() => onSelectClass(className)}>{className}</button>
))}
</div>
<div className="flex items-center justify-between border-t border-slate-200 p-4 dark:border-slate-700">
<span className="text-xs text-slate-500">{classes.length} sınıf</span>
<div className="flex gap-2"><Button size="sm" variant="default" onClick={() => onSelectClass('')}>Temizle</Button><Button size="sm" variant="solid" onClick={onClose}>Tamam</Button></div>
</div>
</div>
</div>
);
};
)
}
export default TailwindModal;
export default TailwindModal

View file

@ -1763,7 +1763,6 @@ export const CUSTOM_COMPONENTS: ComponentDefinition[] = [
hooks: [],
},
];
export const HTML_ELEMENTS: ComponentDefinition[] = [
{
name: "div",
@ -2203,13 +2202,13 @@ export const HTML_ELEMENTS: ComponentDefinition[] = [
name: "className",
type: "string",
value: "",
category: "properties",
category: "styling",
},
{
name: "onClick",
type: "function",
value: "",
category: "properties",
category: "events",
},
],
hooks: [
@ -2220,6 +2219,62 @@ export const HTML_ELEMENTS: ComponentDefinition[] = [
},
],
},
{
name: "h4",
icon: "Heading4",
category: "basic",
description: "Başlık metni (h4)",
properties: [
{
name: "children",
type: "string",
value: "",
description: "Heading text",
category: "properties",
},
{
name: "className",
type: "string",
value: "",
category: "styling",
},
{
name: "onClick",
type: "function",
value: "",
category: "events",
},
],
hooks: [],
},
{
name: "h5",
icon: "Heading5",
category: "basic",
description: "Başlık metni (h5)",
properties: [
{
name: "children",
type: "string",
value: "",
description: "Heading text",
category: "properties",
},
{
name: "className",
type: "string",
value: "",
category: "styling",
},
{
name: "onClick",
type: "function",
value: "",
category: "events",
},
],
hooks: [],
},
{
name: "img",
icon: "Image",
@ -2278,19 +2333,3 @@ export const HTML_ELEMENTS: ComponentDefinition[] = [
],
},
];
export const getAllComponentDefinitions = (): ComponentDefinition[] => {
//return [...HTML_ELEMENTS, ...CUSTOM_COMPONENTS];
return [...CUSTOM_COMPONENTS];
};
export const getComponentDefinition = (
name: string
): ComponentDefinition | undefined => {
return getAllComponentDefinitions().find((comp) => comp.name === name);
};
export interface PanelState {
toolbox: boolean;
properties: boolean;
}

View file

@ -1,297 +0,0 @@
import { TailwindClass } from "../../../proxy/developerKit/componentInfo";
export const TAILWIND_CLASSES: TailwindClass[] = [
{
category: 'Layout',
classes: [
'container', 'box-border', 'box-content',
'block', 'inline-block', 'inline', 'flex', 'inline-flex', 'table', 'inline-table',
'table-caption', 'table-cell', 'table-column', 'table-column-group',
'table-footer-group', 'table-header-group', 'table-row-group', 'table-row',
'flow-root', 'grid', 'inline-grid', 'contents', 'list-item', 'hidden'
]
},
{
category: 'Flexbox & Grid',
classes: [
'basis-0', 'basis-1', 'basis-2', 'basis-3', 'basis-4', 'basis-5', 'basis-6',
'basis-auto', 'basis-px', 'basis-full',
'flex-row', 'flex-row-reverse', 'flex-col', 'flex-col-reverse',
'flex-wrap', 'flex-wrap-reverse', 'flex-nowrap',
'flex-1', 'flex-auto', 'flex-initial', 'flex-none',
'grow', 'grow-0', 'shrink', 'shrink-0',
'order-1', 'order-2', 'order-3', 'order-4', 'order-5', 'order-6',
'order-7', 'order-8', 'order-9', 'order-10', 'order-11', 'order-12',
'order-first', 'order-last', 'order-none',
'grid-cols-1', 'grid-cols-2', 'grid-cols-3', 'grid-cols-4', 'grid-cols-5',
'grid-cols-6', 'grid-cols-7', 'grid-cols-8', 'grid-cols-9', 'grid-cols-10',
'grid-cols-11', 'grid-cols-12', 'grid-cols-none',
'col-auto', 'col-span-1', 'col-span-2', 'col-span-3', 'col-span-4',
'col-span-5', 'col-span-6', 'col-span-7', 'col-span-8', 'col-span-9',
'col-span-10', 'col-span-11', 'col-span-12', 'col-span-full',
'grid-rows-1', 'grid-rows-2', 'grid-rows-3', 'grid-rows-4', 'grid-rows-5',
'grid-rows-6', 'grid-rows-none',
'row-auto', 'row-span-1', 'row-span-2', 'row-span-3', 'row-span-4',
'row-span-5', 'row-span-6', 'row-span-full',
'gap-0', 'gap-1', 'gap-2', 'gap-3', 'gap-4', 'gap-5', 'gap-6', 'gap-7',
'gap-8', 'gap-9', 'gap-10', 'gap-11', 'gap-12', 'gap-14', 'gap-16',
'gap-20', 'gap-24', 'gap-28', 'gap-32', 'gap-36', 'gap-40', 'gap-44',
'gap-48', 'gap-52', 'gap-56', 'gap-60', 'gap-64', 'gap-72', 'gap-80',
'gap-96', 'gap-px'
]
},
{
category: 'Spacing',
classes: [
'm-0', 'm-1', 'm-2', 'm-3', 'm-4', 'm-5', 'm-6', 'm-7', 'm-8', 'm-9',
'm-10', 'm-11', 'm-12', 'm-14', 'm-16', 'm-20', 'm-24', 'm-28', 'm-32',
'm-36', 'm-40', 'm-44', 'm-48', 'm-52', 'm-56', 'm-60', 'm-64', 'm-72',
'm-80', 'm-96', 'm-auto', 'm-px',
'mx-0', 'mx-1', 'mx-2', 'mx-3', 'mx-4', 'mx-5', 'mx-6', 'mx-7', 'mx-8',
'mx-9', 'mx-10', 'mx-11', 'mx-12', 'mx-14', 'mx-16', 'mx-20', 'mx-24',
'mx-28', 'mx-32', 'mx-36', 'mx-40', 'mx-44', 'mx-48', 'mx-52', 'mx-56',
'mx-60', 'mx-64', 'mx-72', 'mx-80', 'mx-96', 'mx-auto', 'mx-px',
'my-0', 'my-1', 'my-2', 'my-3', 'my-4', 'my-5', 'my-6', 'my-7', 'my-8',
'my-9', 'my-10', 'my-11', 'my-12', 'my-14', 'my-16', 'my-20', 'my-24',
'my-28', 'my-32', 'my-36', 'my-40', 'my-44', 'my-48', 'my-52', 'my-56',
'my-60', 'my-64', 'my-72', 'my-80', 'my-96', 'my-auto', 'my-px',
'p-0', 'p-1', 'p-2', 'p-3', 'p-4', 'p-5', 'p-6', 'p-7', 'p-8', 'p-9',
'p-10', 'p-11', 'p-12', 'p-14', 'p-16', 'p-20', 'p-24', 'p-28', 'p-32',
'p-36', 'p-40', 'p-44', 'p-48', 'p-52', 'p-56', 'p-60', 'p-64', 'p-72',
'p-80', 'p-96', 'p-px',
'px-0', 'px-1', 'px-2', 'px-3', 'px-4', 'px-5', 'px-6', 'px-7', 'px-8',
'px-9', 'px-10', 'px-11', 'px-12', 'px-14', 'px-16', 'px-20', 'px-24',
'px-28', 'px-32', 'px-36', 'px-40', 'px-44', 'px-48', 'px-52', 'px-56',
'px-60', 'px-64', 'px-72', 'px-80', 'px-96', 'px-px',
'py-0', 'py-1', 'py-2', 'py-3', 'py-4', 'py-5', 'py-6', 'py-7', 'py-8',
'py-9', 'py-10', 'py-11', 'py-12', 'py-14', 'py-16', 'py-20', 'py-24',
'py-28', 'py-32', 'py-36', 'py-40', 'py-44', 'py-48', 'py-52', 'py-56',
'py-60', 'py-64', 'py-72', 'py-80', 'py-96', 'py-px'
]
},
{
category: 'Sizing',
classes: [
'w-0', 'w-1', 'w-2', 'w-3', 'w-4', 'w-5', 'w-6', 'w-7', 'w-8', 'w-9',
'w-10', 'w-11', 'w-12', 'w-14', 'w-16', 'w-20', 'w-24', 'w-28', 'w-32',
'w-36', 'w-40', 'w-44', 'w-48', 'w-52', 'w-56', 'w-60', 'w-64', 'w-72',
'w-80', 'w-96', 'w-auto', 'w-px', 'w-0.5', 'w-1.5', 'w-2.5', 'w-3.5',
'w-1/2', 'w-1/3', 'w-2/3', 'w-1/4', 'w-2/4', 'w-3/4', 'w-1/5', 'w-2/5',
'w-3/5', 'w-4/5', 'w-1/6', 'w-2/6', 'w-3/6', 'w-4/6', 'w-5/6', 'w-1/12',
'w-2/12', 'w-3/12', 'w-4/12', 'w-5/12', 'w-6/12', 'w-7/12', 'w-8/12',
'w-9/12', 'w-10/12', 'w-11/12', 'w-full', 'w-screen', 'w-min', 'w-max',
'w-fit',
'h-0', 'h-1', 'h-2', 'h-3', 'h-4', 'h-5', 'h-6', 'h-7', 'h-8', 'h-9',
'h-10', 'h-11', 'h-12', 'h-14', 'h-16', 'h-20', 'h-24', 'h-28', 'h-32',
'h-36', 'h-40', 'h-44', 'h-48', 'h-52', 'h-56', 'h-60', 'h-64', 'h-72',
'h-80', 'h-96', 'h-auto', 'h-px', 'h-0.5', 'h-1.5', 'h-2.5', 'h-3.5',
'h-1/2', 'h-1/3', 'h-2/3', 'h-1/4', 'h-2/4', 'h-3/4', 'h-1/5', 'h-2/5',
'h-3/5', 'h-4/5', 'h-1/6', 'h-2/6', 'h-3/6', 'h-4/6', 'h-5/6', 'h-full',
'h-screen', 'h-min', 'h-max', 'h-fit'
]
},
{
category: 'Typography',
classes: [
'font-sans', 'font-serif', 'font-mono',
'text-xs', 'text-sm', 'text-base', 'text-lg', 'text-xl', 'text-2xl',
'text-3xl', 'text-4xl', 'text-5xl', 'text-6xl', 'text-7xl', 'text-8xl',
'text-9xl',
'font-thin', 'font-extralight', 'font-light', 'font-normal', 'font-medium',
'font-semibold', 'font-bold', 'font-extrabold', 'font-black',
'italic', 'not-italic',
'underline', 'overline', 'line-through', 'no-underline',
'text-left', 'text-center', 'text-right', 'text-justify',
'text-black', 'text-white', 'text-gray-50', 'text-gray-100', 'text-gray-200',
'text-gray-300', 'text-gray-400', 'text-gray-500', 'text-gray-600',
'text-gray-700', 'text-gray-800', 'text-gray-900', 'text-red-50',
'text-red-100', 'text-red-200', 'text-red-300', 'text-red-400',
'text-red-500', 'text-red-600', 'text-red-700', 'text-red-800',
'text-red-900', 'text-blue-50', 'text-blue-100', 'text-blue-200',
'text-blue-300', 'text-blue-400', 'text-blue-500', 'text-blue-600',
'text-blue-700', 'text-blue-800', 'text-blue-900'
]
},
{
category: 'Background',
classes: [
'bg-transparent', 'bg-current', 'bg-black', 'bg-white',
'bg-gray-50', 'bg-gray-100', 'bg-gray-200', 'bg-gray-300', 'bg-gray-400',
'bg-gray-500', 'bg-gray-600', 'bg-gray-700', 'bg-gray-800', 'bg-gray-900',
'bg-red-50', 'bg-red-100', 'bg-red-200', 'bg-red-300', 'bg-red-400',
'bg-red-500', 'bg-red-600', 'bg-red-700', 'bg-red-800', 'bg-red-900',
'bg-yellow-50', 'bg-yellow-100', 'bg-yellow-200', 'bg-yellow-300',
'bg-yellow-400', 'bg-yellow-500', 'bg-yellow-600', 'bg-yellow-700',
'bg-yellow-800', 'bg-yellow-900',
'bg-green-50', 'bg-green-100', 'bg-green-200', 'bg-green-300',
'bg-green-400', 'bg-green-500', 'bg-green-600', 'bg-green-700',
'bg-green-800', 'bg-green-900',
'bg-blue-50', 'bg-blue-100', 'bg-blue-200', 'bg-blue-300', 'bg-blue-400',
'bg-blue-500', 'bg-blue-600', 'bg-blue-700', 'bg-blue-800', 'bg-blue-900',
'bg-indigo-50', 'bg-indigo-100', 'bg-indigo-200', 'bg-indigo-300',
'bg-indigo-400', 'bg-indigo-500', 'bg-indigo-600', 'bg-indigo-700',
'bg-indigo-800', 'bg-indigo-900',
'bg-purple-50', 'bg-purple-100', 'bg-purple-200', 'bg-purple-300',
'bg-purple-400', 'bg-purple-500', 'bg-purple-600', 'bg-purple-700',
'bg-purple-800', 'bg-purple-900',
'bg-pink-50', 'bg-pink-100', 'bg-pink-200', 'bg-pink-300', 'bg-pink-400',
'bg-pink-500', 'bg-pink-600', 'bg-pink-700', 'bg-pink-800', 'bg-pink-900'
]
},
{
category: 'Border',
classes: [
'border-0', 'border-2', 'border-4', 'border-8', 'border',
'border-t-0', 'border-t-2', 'border-t-4', 'border-t-8', 'border-t',
'border-r-0', 'border-r-2', 'border-r-4', 'border-r-8', 'border-r',
'border-b-0', 'border-b-2', 'border-b-4', 'border-b-8', 'border-b',
'border-l-0', 'border-l-2', 'border-l-4', 'border-l-8', 'border-l',
'border-solid', 'border-dashed', 'border-dotted', 'border-double',
'border-none',
'border-transparent', 'border-current', 'border-black', 'border-white',
'border-gray-50', 'border-gray-100', 'border-gray-200', 'border-gray-300',
'border-gray-400', 'border-gray-500', 'border-gray-600', 'border-gray-700',
'border-gray-800', 'border-gray-900',
'rounded-none', 'rounded-sm', 'rounded', 'rounded-md', 'rounded-lg',
'rounded-xl', 'rounded-2xl', 'rounded-3xl', 'rounded-full',
'rounded-t-none', 'rounded-t-sm', 'rounded-t', 'rounded-t-md',
'rounded-t-lg', 'rounded-t-xl', 'rounded-t-2xl', 'rounded-t-3xl',
'rounded-t-full'
]
},
{
category: 'Effects',
classes: [
'shadow-sm', 'shadow', 'shadow-md', 'shadow-lg', 'shadow-xl',
'shadow-2xl', 'shadow-inner', 'shadow-none',
'opacity-0', 'opacity-5', 'opacity-10', 'opacity-20', 'opacity-25',
'opacity-30', 'opacity-40', 'opacity-50', 'opacity-60', 'opacity-70',
'opacity-75', 'opacity-80', 'opacity-90', 'opacity-95', 'opacity-100',
'blur-none', 'blur-sm', 'blur', 'blur-md', 'blur-lg', 'blur-xl',
'blur-2xl', 'blur-3xl',
'brightness-0', 'brightness-50', 'brightness-75', 'brightness-90',
'brightness-95', 'brightness-100', 'brightness-105', 'brightness-110',
'brightness-125', 'brightness-150', 'brightness-200'
]
},
{
category: 'Positioning',
classes: [
'static', 'fixed', 'absolute', 'relative', 'sticky',
'inset-0', 'inset-x-0', 'inset-y-0', 'top-0', 'right-0', 'bottom-0',
'left-0', 'inset-px', 'inset-x-px', 'inset-y-px', 'top-px', 'right-px',
'bottom-px', 'left-px', 'inset-0.5', 'inset-1', 'inset-1.5', 'inset-2',
'inset-2.5', 'inset-3', 'inset-3.5', 'inset-4', 'inset-5', 'inset-6',
'inset-7', 'inset-8', 'inset-9', 'inset-10', 'inset-11', 'inset-12',
'inset-14', 'inset-16', 'inset-20', 'inset-24', 'inset-28', 'inset-32',
'inset-36', 'inset-40', 'inset-44', 'inset-48', 'inset-52', 'inset-56',
'inset-60', 'inset-64', 'inset-72', 'inset-80', 'inset-96', 'inset-auto',
'inset-1/2', 'inset-1/3', 'inset-2/3', 'inset-1/4', 'inset-2/4',
'inset-3/4', 'inset-full',
'z-0', 'z-10', 'z-20', 'z-30', 'z-40', 'z-50', 'z-auto'
]
},
{
category: 'Filters',
classes: [
'filter', 'filter-none', 'contrast-0', 'contrast-50',
'contrast-75', 'contrast-100', 'contrast-125', 'contrast-150',
'contrast-200', 'drop-shadow-sm', 'drop-shadow', 'drop-shadow-md',
'drop-shadow-lg', 'drop-shadow-xl', 'drop-shadow-2xl', 'drop-shadow-none',
'grayscale-0', 'grayscale', 'hue-rotate-0', 'hue-rotate-15',
'hue-rotate-30', 'hue-rotate-60', 'hue-rotate-90', 'hue-rotate-180',
'invert-0', 'invert', 'saturate-0', 'saturate-50', 'saturate-100',
'saturate-150', 'saturate-200', 'sepia-0', 'sepia'
]
},
{
category: 'Interactivity',
classes: [
'cursor-auto', 'cursor-default', 'cursor-pointer', 'cursor-wait',
'cursor-text', 'cursor-move', 'cursor-help', 'cursor-not-allowed',
'cursor-none', 'cursor-context-menu', 'cursor-progress', 'cursor-cell',
'cursor-crosshair', 'cursor-vertical-text', 'cursor-alias', 'cursor-copy',
'cursor-no-drop', 'cursor-grab', 'cursor-grabbing', 'cursor-all-scroll',
'cursor-col-resize', 'cursor-row-resize', 'cursor-n-resize',
'cursor-e-resize', 'cursor-s-resize', 'cursor-w-resize', 'cursor-ne-resize',
'cursor-nw-resize', 'cursor-se-resize', 'cursor-sw-resize',
'cursor-ew-resize', 'cursor-ns-resize', 'cursor-nesw-resize',
'cursor-nwse-resize', 'cursor-zoom-in', 'cursor-zoom-out',
'select-none', 'select-text', 'select-all', 'select-auto',
'resize-none', 'resize-y', 'resize-x', 'resize',
'scroll-auto', 'scroll-smooth',
'snap-none', 'snap-x', 'snap-y', 'snap-both', 'snap-mandatory',
'snap-proximity',
'touch-auto', 'touch-none', 'touch-pan-x', 'touch-pan-left',
'touch-pan-right', 'touch-pan-y', 'touch-pan-up', 'touch-pan-down',
'touch-pinch-zoom', 'touch-manipulation',
'pointer-events-none', 'pointer-events-auto'
]
},
{
category: 'Transitions',
classes: [
'transition-none', 'transition-all', 'transition', 'transition-colors',
'transition-opacity', 'transition-shadow', 'transition-transform',
'duration-75', 'duration-100', 'duration-150', 'duration-200',
'duration-300', 'duration-500', 'duration-700', 'duration-1000',
'ease-linear', 'ease-in', 'ease-out', 'ease-in-out',
'delay-75', 'delay-100', 'delay-150', 'delay-200', 'delay-300',
'delay-500', 'delay-700', 'delay-1000',
'animate-none', 'animate-spin', 'animate-ping', 'animate-pulse',
'animate-bounce'
]
},
{
category: 'Transforms',
classes: [
'transform', 'transform-gpu', 'transform-none',
'scale-0', 'scale-50', 'scale-75', 'scale-90', 'scale-95', 'scale-100',
'scale-105', 'scale-110', 'scale-125', 'scale-150',
'scale-x-0', 'scale-x-50', 'scale-x-75', 'scale-x-90', 'scale-x-95',
'scale-x-100', 'scale-x-105', 'scale-x-110', 'scale-x-125', 'scale-x-150',
'scale-y-0', 'scale-y-50', 'scale-y-75', 'scale-y-90', 'scale-y-95',
'scale-y-100', 'scale-y-105', 'scale-y-110', 'scale-y-125', 'scale-y-150',
'rotate-0', 'rotate-1', 'rotate-2', 'rotate-3', 'rotate-6', 'rotate-12',
'rotate-45', 'rotate-90', 'rotate-180', '-rotate-180', '-rotate-90',
'-rotate-45', '-rotate-12', '-rotate-6', '-rotate-3', '-rotate-2',
'-rotate-1',
'translate-x-0', 'translate-x-1', 'translate-x-2', 'translate-x-3',
'translate-x-4', 'translate-x-5', 'translate-x-6', 'translate-x-7',
'translate-x-8', 'translate-x-9', 'translate-x-10', 'translate-x-11',
'translate-x-12', 'translate-x-14', 'translate-x-16', 'translate-x-20',
'translate-x-24', 'translate-x-28', 'translate-x-32', 'translate-x-36',
'translate-x-40', 'translate-x-44', 'translate-x-48', 'translate-x-52',
'translate-x-56', 'translate-x-60', 'translate-x-64', 'translate-x-72',
'translate-x-80', 'translate-x-96', 'translate-x-px', 'translate-x-0.5',
'translate-x-1.5', 'translate-x-2.5', 'translate-x-3.5',
'translate-x-1/2', 'translate-x-1/3', 'translate-x-2/3', 'translate-x-1/4',
'translate-x-2/4', 'translate-x-3/4', 'translate-x-full',
'translate-y-0', 'translate-y-1', 'translate-y-2', 'translate-y-3',
'translate-y-4', 'translate-y-5', 'translate-y-6', 'translate-y-7',
'translate-y-8', 'translate-y-9', 'translate-y-10', 'translate-y-11',
'translate-y-12', 'translate-y-14', 'translate-y-16', 'translate-y-20',
'translate-y-24', 'translate-y-28', 'translate-y-32', 'translate-y-36',
'translate-y-40', 'translate-y-44', 'translate-y-48', 'translate-y-52',
'translate-y-56', 'translate-y-60', 'translate-y-64', 'translate-y-72',
'translate-y-80', 'translate-y-96', 'translate-y-px', 'translate-y-0.5',
'translate-y-1.5', 'translate-y-2.5', 'translate-y-3.5',
'translate-y-1/2', 'translate-y-1/3', 'translate-y-2/3', 'translate-y-1/4',
'translate-y-2/4', 'translate-y-3/4', 'translate-y-full',
'skew-x-0', 'skew-x-1', 'skew-x-2', 'skew-x-3', 'skew-x-6', 'skew-x-12',
'skew-y-0', 'skew-y-1', 'skew-y-2', 'skew-y-3', 'skew-y-6', 'skew-y-12'
]
}
];
export const searchTailwindClasses = (query: string): string[] => {
const allClasses = TAILWIND_CLASSES.flatMap(category => category.classes);
return allClasses.filter(className =>
className.toLowerCase().includes(query.toLowerCase())
);
};
export const getClassesByCategory = (category: string): string[] => {
const categoryData = TAILWIND_CLASSES.find(c => c.category === category);
return categoryData ? categoryData.classes : [];
};

View file

@ -1,13 +0,0 @@
import React from 'react';
declare interface Window {
ReactDOM?: {
render: (element: React.ReactNode, container: Element) => void;
createRoot?: (container: Element) => {
render: (element: React.ReactNode) => void;
unmount: () => void;
};
};
}
// Add other global declarations your app needs here

View file

@ -1,31 +0,0 @@
import { Monaco } from "@monaco-editor/react";
export function setupMonacoReact(monaco: Monaco) {
// TypeScript ayarları
monaco.languages.typescript.typescriptDefaults.setCompilerOptions({
target: monaco.languages.typescript.ScriptTarget.ESNext,
module: monaco.languages.typescript.ModuleKind.ESNext,
jsx: monaco.languages.typescript.JsxEmit.React,
moduleResolution: monaco.languages.typescript.ModuleResolutionKind.NodeJs,
allowJs: true,
esModuleInterop: true,
allowSyntheticDefaultImports: true,
noEmit: true,
});
// React tiplerini manuel olarak ekle
const reactDTS = `
declare module "react" {
import { ReactNode } from 'react';
export type FC<P = {}> = (props: P & { children?: ReactNode }) => ReactNode;
export type ReactNode = any;
export default any;
}
`;
monaco.languages.typescript.typescriptDefaults.addExtraLib(
reactDTS,
"file:///node_modules/@types/react/index.d.ts"
);
}

View file

@ -1,6 +1,12 @@
import React, { useEffect, useState } from 'react'
import * as Babel from '@babel/standalone'
import axios from 'axios'
import DOMPurify from 'dompurify'
import apiService from '@/services/api.service'
import * as UiKit from '@/components/ui'
import { generateDesignerCode } from '@/components/visualDesigner/codeGenerator'
import type { DesignerDocument } from '@/components/visualDesigner/types'
import type { CustomComponent } from '@/proxy/developerKit/models'
import { useComponents } from '../../contexts/ComponentContext'
import ErrorBoundary from './ErrorBoundary'
import { toast } from '../ui'
@ -17,6 +23,8 @@ const compileComponent = (code: string, scope: Record<string, any> = {}) => {
const require = (moduleName: string) => {
if (moduleName === 'react') return React
if (moduleName === 'axios') return axios
if (moduleName === 'dompurify') return DOMPurify
if (moduleName === '@/services/api.service') return apiService
throw new Error(`Modül bulunamadı: ${moduleName}`)
}
@ -39,49 +47,74 @@ interface DynamicRendererProps {
dependencies?: string[]
}
const staticComponents: Record<string, any> = {
...UiKit,
Alert: React.lazy(() => import('../ui/Alert')),
Avatar: React.lazy(() => import('../ui/Avatar')),
Badge: React.lazy(() => import('../ui/Badge')),
Button: React.lazy(() => import('../ui/Button')),
Calendar: React.lazy(() => import('../ui/Calendar')),
Card: React.lazy(() => import('../ui/Card')),
Checkbox: React.lazy(() => import('../ui/Checkbox')),
ConfigProvider: React.lazy(() => import('../ui/ConfigProvider')),
DatePicker: React.lazy(() => import('../ui/DatePicker')),
Dialog: React.lazy(() => import('../ui/Dialog')),
Drawer: React.lazy(() => import('../ui/Drawer')),
Dropdown: React.lazy(() => import('../ui/Dropdown')),
FormItem: React.lazy(() => import('../ui/Form/FormItem')),
FormContainer: React.lazy(() => import('../ui/Form/FormContainer')),
Input: React.lazy(() => import('../ui/Input')),
InputGroup: React.lazy(() => import('../ui/InputGroup')),
Menu: React.lazy(() => import('../ui/Menu')),
MenuItem: React.lazy(() => import('../ui/MenuItem')),
Notification: React.lazy(() => import('../ui/Notification')),
Pagination: React.lazy(() => import('../ui/Pagination')),
Progress: React.lazy(() => import('../ui/Progress')),
Radio: React.lazy(() => import('../ui/Radio')),
RangeCalendar: React.lazy(() => import('../ui/RangeCalendar')),
ScrollBar: React.lazy(() => import('../ui/ScrollBar')),
Segment: React.lazy(() => import('../ui/Segment')),
Select: React.lazy(() => import('../ui/Select')),
Skeleton: React.lazy(() => import('../ui/Skeleton')),
Spinner: React.lazy(() => import('../ui/Spinner')),
Steps: React.lazy(() => import('../ui/Steps')),
Switcher: React.lazy(() => import('../ui/Switcher')),
Table: React.lazy(() => import('../ui/Table')),
Tabs: React.lazy(() => import('../ui/Tabs')),
Tag: React.lazy(() => import('../ui/Tag')),
TimeInput: React.lazy(() => import('../ui/TimeInput')),
Timeline: React.lazy(() => import('../ui/Timeline')),
Tooltip: React.lazy(() => import('../ui/Tooltip')),
Upload: React.lazy(() => import('../ui/Upload')),
PlatformViewHost: React.lazy(() => import('./PlatformViewHost')),
toast,
apiService,
DOMPurify,
UiKit,
}
const getRuntimeCode = (component: CustomComponent) => {
if (!component) return ''
try {
const props = component.props ? JSON.parse(component.props) : null
const document = props?.visualDesigner as DesignerDocument | undefined
if (
document?.version === 1 &&
document.sourceMode === 'visual' &&
Array.isArray(document.nodes)
) {
return generateDesignerCode(component.name, document)
}
} catch (error) {
console.warn(`Visual designer schema could not be read for ${component.name}:`, error)
}
return component.code
}
const DynamicRenderer: React.FC<DynamicRendererProps> = ({
componentName,
dependencies: externalDeps,
}) => {
// Lazy load for UI components
const Alert = React.lazy(() => import('../ui/Alert'))
const Avatar = React.lazy(() => import('../ui/Avatar'))
const Badge = React.lazy(() => import('../ui/Badge'))
const Button = React.lazy(() => import('../ui/Button'))
const Calendar = React.lazy(() => import('../ui/Calendar'))
const Card = React.lazy(() => import('../ui/Card'))
const Checkbox = React.lazy(() => import('../ui/Checkbox'))
const ConfigProvider = React.lazy(() => import('../ui/ConfigProvider'))
const DatePicker = React.lazy(() => import('../ui/DatePicker'))
const Dialog = React.lazy(() => import('../ui/Dialog'))
const Drawer = React.lazy(() => import('../ui/Drawer'))
const Dropdown = React.lazy(() => import('../ui/Dropdown'))
const FormItem = React.lazy(() => import('../ui/Form/FormItem'))
const FormContainer = React.lazy(() => import('../ui/Form/FormContainer'))
const Input = React.lazy(() => import('../ui/Input'))
const InputGroup = React.lazy(() => import('../ui/InputGroup'))
const Menu = React.lazy(() => import('../ui/Menu'))
const MenuItem = React.lazy(() => import('../ui/MenuItem'))
const Notification = React.lazy(() => import('../ui/Notification'))
const Pagination = React.lazy(() => import('../ui/Pagination'))
const Progress = React.lazy(() => import('../ui/Progress'))
const Radio = React.lazy(() => import('../ui/Radio'))
const RangeCalendar = React.lazy(() => import('../ui/RangeCalendar'))
const ScrollBar = React.lazy(() => import('../ui/ScrollBar'))
const Segment = React.lazy(() => import('../ui/Segment'))
const Select = React.lazy(() => import('../ui/Select'))
const Skeleton = React.lazy(() => import('../ui/Skeleton'))
const Spinner = React.lazy(() => import('../ui/Spinner'))
const Steps = React.lazy(() => import('../ui/Steps'))
const Switcher = React.lazy(() => import('../ui/Switcher'))
const Table = React.lazy(() => import('../ui/Table'))
const Tabs = React.lazy(() => import('../ui/Tabs'))
const Tag = React.lazy(() => import('../ui/Tag'))
const TimeInput = React.lazy(() => import('../ui/TimeInput'))
const Timeline = React.lazy(() => import('../ui/Timeline'))
const Tooltip = React.lazy(() => import('../ui/Tooltip'))
const Upload = React.lazy(() => import('../ui/Upload'))
const [Component, setComponent] = useState<React.ComponentType<any> | null>(null)
const { getComponentByName, components } = useComponents()
@ -105,7 +138,7 @@ const DynamicRenderer: React.FC<DynamicRendererProps> = ({
c.name,
{
name: c.name,
jsx_code: c.code,
jsx_code: getRuntimeCode(c),
dependencies: Array.isArray(componentDeps) ? componentDeps : [],
},
]
@ -114,47 +147,6 @@ const DynamicRenderer: React.FC<DynamicRendererProps> = ({
const compiled: Record<string, any> = {}
const staticComponents: Record<string, any> = {
Alert,
Avatar,
Badge,
Button,
Calendar,
Card,
Checkbox,
ConfigProvider,
DatePicker,
Dialog,
Drawer,
Dropdown,
FormItem,
FormContainer,
Input,
InputGroup,
Menu,
MenuItem,
Notification,
Pagination,
Progress,
Radio,
RangeCalendar,
ScrollBar,
Segment,
Select,
Skeleton,
Spinner,
Steps,
Switcher,
Table,
Tabs,
Tag,
TimeInput,
Timeline,
toast,
Tooltip,
Upload,
}
const compileWithDependencies = (name: string): any => {
if (compiled[name]) return compiled[name]

View file

@ -0,0 +1,114 @@
import React, { Suspense, useCallback, useEffect, useState } from 'react'
import type { GridDto } from '@/proxy/form/models'
import { getList } from '@/services/form.service'
import { Loading } from '@/components/shared'
const Grid = React.lazy(() => import('@/views/list/Grid'))
const List = React.lazy(() => import('@/views/list/List'))
const Tree = React.lazy(() => import('@/views/list/Tree'))
const GanttView = React.lazy(() => import('@/views/list/GanttView'))
const TodoBoard = React.lazy(() => import('@/views/list/TodoBoard'))
const CardView = React.lazy(() => import('@/views/list/CardView'))
const SchedulerView = React.lazy(() => import('@/views/list/SchedulerView'))
const Pivot = React.lazy(() => import('@/views/list/Pivot'))
const Chart = React.lazy(() => import('@/views/list/Chart'))
export type PlatformViewName =
| 'List'
| 'Grid'
| 'Tree'
| 'GanttView'
| 'TodoBoard'
| 'CardView'
| 'SchedulerView'
| 'Pivot'
| 'Chart'
interface PlatformViewHostProps {
view: PlatformViewName
listFormCode: string
height?: string
}
const PlatformViewHost = ({ view, listFormCode, height = '520px' }: PlatformViewHostProps) => {
const [gridDto, setGridDto] = useState<GridDto>()
const [error, setError] = useState('')
const refreshGridDto = useCallback(async () => {
if (!listFormCode.trim()) return
try {
setError('')
const response = await getList({ listFormCode })
const nextGridDto = response?.data
if (!nextGridDto?.gridOptions) {
throw new Error(`List form "${listFormCode}" did not return a valid grid configuration.`)
}
setGridDto(nextGridDto)
} catch (reason) {
console.error(`Platform view ${view} could not load:`, reason)
setGridDto(undefined)
setError(`"${listFormCode}" list form configuration could not be loaded.`)
}
}, [listFormCode, view])
useEffect(() => {
setGridDto(undefined)
void refreshGridDto()
}, [refreshGridDto])
if (!listFormCode.trim()) {
return (
<div className="rounded-lg border border-dashed border-amber-300 bg-amber-50 p-5 text-sm text-amber-800">
{view} için listFormCode propertysini ayarlayın.
</div>
)
}
if (error) return <div className="rounded-lg bg-red-50 p-4 text-sm text-red-700">{error}</div>
if (!gridDto) return <Loading loading />
const sharedProps = { listFormCode, isSubForm: false, gridDto }
const content = (() => {
switch (view) {
case 'List':
return (
<List
gridDto={gridDto}
listFormCode={listFormCode}
onRefreshGridDto={refreshGridDto}
/>
)
case 'Tree':
return <Tree {...sharedProps} />
case 'GanttView':
return <GanttView {...sharedProps} />
case 'TodoBoard':
return <TodoBoard {...sharedProps} />
case 'CardView':
return <CardView {...sharedProps} />
case 'SchedulerView':
return <SchedulerView {...sharedProps} />
case 'Pivot':
return <Pivot {...sharedProps} refreshGridDto={refreshGridDto} />
case 'Chart':
return (
<Chart
{...sharedProps}
id={gridDto.gridOptions.id as string}
refreshGridDto={refreshGridDto}
/>
)
default:
return <Grid {...sharedProps} />
}
})()
return (
<div className="overflow-auto" style={{ minHeight: height }}>
<Suspense fallback={<Loading loading />}>{content}</Suspense>
</div>
)
}
export default PlatformViewHost

View file

@ -1,9 +1,11 @@
import { forwardRef } from 'react'
import { forwardRef, useEffect, useState } from 'react'
import classNames from 'classnames'
import { MenuContextProvider } from './context/menuContext'
import { useConfig } from '../ConfigProvider'
import type { CommonProps, TypeAttributes } from '../@types/common'
const EMPTY_ACTIVE_KEYS: string[] = []
export interface MenuProps extends CommonProps {
defaultActiveKeys?: Array<string>
defaultExpandedKeys?: Array<string>
@ -18,18 +20,29 @@ const Menu = forwardRef<HTMLElement, MenuProps>((props, ref) => {
const {
children,
className,
defaultActiveKeys = [],
defaultActiveKeys = EMPTY_ACTIVE_KEYS,
defaultExpandedKeys = [],
menuItemHeight = 30,
onSelect,
sideCollapsed = false,
variant = 'light',
variant: variantProp,
...rest
} = props
const menuDefaultClass = 'menu'
const { themeColor, primaryColorLevel } = useConfig()
const { themeColor, primaryColorLevel, mode } = useConfig()
const variant = variantProp ?? mode
const [activeKeys, setActiveKeys] = useState(defaultActiveKeys)
useEffect(() => {
setActiveKeys(defaultActiveKeys)
}, [defaultActiveKeys])
const handleSelect = (eventKey: string, event: MouseEvent) => {
setActiveKeys([eventKey])
onSelect?.(eventKey, event)
}
const menuColor = () => {
if (variant === 'themed') {
@ -44,12 +57,12 @@ const Menu = forwardRef<HTMLElement, MenuProps>((props, ref) => {
<nav ref={ref} className={menuClass} {...rest}>
<MenuContextProvider
value={{
onSelect,
onSelect: handleSelect,
menuItemHeight,
variant,
sideCollapsed,
defaultExpandedKeys,
defaultActiveKeys,
defaultActiveKeys: activeKeys,
}}
>
{children}

View file

@ -3,297 +3,292 @@ import classNames from 'classnames'
import ReactSelect, { components as ReactSelectComponents } from 'react-select'
import CreatableSelect from 'react-select/creatable'
import AsyncSelect from 'react-select/async'
import { TW_COLORS, BORDER_RADIUS, SPACING } from '@/utils/tailwind'
import { TW_COLORS, BORDER_RADIUS } from '@/utils/tailwind'
import isEmpty from 'lodash/isEmpty'
import get from 'lodash/get'
import { useConfig } from '../ConfigProvider'
import { useForm } from '../Form/context'
import { useInputGroup } from '../InputGroup/context'
import { FaCheck, FaChevronDown, FaTimes } from 'react-icons/fa';
import { FaCheck, FaChevronDown, FaTimes } from 'react-icons/fa'
import Spinner from '../Spinner/Spinner'
import { CONTROL_SIZES } from '../utils/constants'
import type { CommonProps, TypeAttributes } from '../@types/common'
import type {
ControlProps,
Props as ReactSelectProps,
GroupBase,
MenuListProps,
ControlProps,
Props as ReactSelectProps,
GroupBase,
MenuListProps,
} from 'react-select'
import type { AsyncProps } from 'react-select/async'
import type { CreatableProps } from 'react-select/creatable'
import type { ForwardedRef, ReactNode, Ref } from 'react'
import type { ForwardedRef, JSX, ReactNode, Ref } from 'react'
interface DefaultOptionProps {
innerProps: JSX.IntrinsicElements['div']
label: string
selectProps: { themeColor?: string }
children?: ReactNode
isSelected: boolean
isDisabled: boolean
isFocused: boolean
innerProps: JSX.IntrinsicElements['div']
label: string
selectProps: { themeColor?: string }
children?: ReactNode
isSelected: boolean
isDisabled: boolean
isFocused: boolean
}
const SELECT_SIZE_METRICS = {
xs: { height: 24, fontSize: 12, horizontalPadding: 6 },
sm: { height: 32, fontSize: 14, horizontalPadding: 8 },
md: { height: 40, fontSize: 14, horizontalPadding: 10 },
lg: { height: 48, fontSize: 16, horizontalPadding: 12 },
} as const
const DefaultOption = ({
innerProps,
label,
selectProps,
isSelected,
isDisabled,
isFocused,
children,
innerProps,
label,
selectProps,
isSelected,
isDisabled,
isFocused,
children,
}: DefaultOptionProps) => {
const { themeColor } = selectProps
return (
<div
className={classNames(
'select-option',
isSelected && 'selected',
isDisabled && 'disabled',
isFocused && 'focused'
)}
{...innerProps}
>
<div className="ml-2 min-w-0">{children ?? label}</div>
{isSelected && (
<FaCheck
className={`text-${themeColor} dark:text-white text-xl`}
/>
)}
</div>
)
const { themeColor } = selectProps
return (
<div
className={classNames(
'select-option',
isSelected && 'selected',
isDisabled && 'disabled',
isFocused && 'focused',
)}
{...innerProps}
>
<div className="ml-2 min-w-0">{children ?? label}</div>
{isSelected && <FaCheck className={`text-${themeColor} dark:text-white text-xl`} />}
</div>
)
}
const DefaultMenuList = <
Option,
IsMulti extends boolean = false,
Group extends GroupBase<Option> = GroupBase<Option>
Option,
IsMulti extends boolean = false,
Group extends GroupBase<Option> = GroupBase<Option>,
>(
props: MenuListProps<Option, IsMulti, Group>
props: MenuListProps<Option, IsMulti, Group>,
) => {
const menuListRef = useRef<HTMLDivElement | null>(null)
const menuListRef = useRef<HTMLDivElement | null>(null)
useEffect(() => {
const frame = requestAnimationFrame(() => {
menuListRef.current
?.querySelector('.select-option.selected')
?.scrollIntoView({ block: 'center' })
})
useEffect(() => {
const frame = requestAnimationFrame(() => {
menuListRef.current
?.querySelector('.select-option.selected')
?.scrollIntoView({ block: 'center' })
})
return () => cancelAnimationFrame(frame)
}, [props.selectProps.value])
return () => cancelAnimationFrame(frame)
}, [props.selectProps.value])
return (
<ReactSelectComponents.MenuList
{...props}
innerRef={(element) => {
menuListRef.current = element
return (
<ReactSelectComponents.MenuList
{...props}
innerRef={(element) => {
menuListRef.current = element
if (typeof props.innerRef === 'function') {
props.innerRef(element)
}
}}
/>
)
if (typeof props.innerRef === 'function') {
props.innerRef(element)
}
}}
/>
)
}
const DefaultDropdownIndicator = () => {
return (
<div className="select-dropdown-indicator">
<FaChevronDown />
</div>
)
return (
<div className="select-dropdown-indicator">
<FaChevronDown />
</div>
)
}
interface DefaultClearIndicatorProps {
innerProps: JSX.IntrinsicElements['div']
ref: Ref<HTMLElement>
innerProps: JSX.IntrinsicElements['div']
ref: Ref<HTMLElement>
}
const DefaultClearIndicator = ({
innerProps: { ref, ...restInnerProps },
innerProps: { ref, ...restInnerProps },
}: DefaultClearIndicatorProps) => {
return (
<div {...restInnerProps} ref={ref}>
<div className="select-clear-indicator">
<FaTimes />
</div>
</div>
)
return (
<div {...restInnerProps} ref={ref}>
<div className="select-clear-indicator">
<FaTimes />
</div>
</div>
)
}
interface DefaultLoadingIndicatorProps {
selectProps: { themeColor?: string }
selectProps: { themeColor?: string }
}
const DefaultLoadingIndicator = ({
selectProps,
}: DefaultLoadingIndicatorProps) => {
const { themeColor } = selectProps
return (
<Spinner className={`select-loading-indicatior text-${themeColor}`} />
)
const DefaultLoadingIndicator = ({ selectProps }: DefaultLoadingIndicatorProps) => {
const { themeColor } = selectProps
return <Spinner className={`select-loading-indicatior text-${themeColor}`} />
}
export interface SelectProps<
Option,
IsMulti extends boolean = false,
Group extends GroupBase<Option> = GroupBase<Option>
> extends CommonProps,
ReactSelectProps<Option, IsMulti, Group>,
AsyncProps<Option, IsMulti, Group>,
CreatableProps<Option, IsMulti, Group> {
size?: TypeAttributes.ControlSize
// eslint-disable-next-line @typescript-eslint/no-explicit-any
field?: any
// eslint-disable-next-line @typescript-eslint/no-explicit-any
form?: any
componentAs?: ReactSelect | CreatableSelect | AsyncSelect
Option,
IsMulti extends boolean = false,
Group extends GroupBase<Option> = GroupBase<Option>,
>
extends
CommonProps,
ReactSelectProps<Option, IsMulti, Group>,
AsyncProps<Option, IsMulti, Group>,
CreatableProps<Option, IsMulti, Group> {
size?: TypeAttributes.ControlSize
field?: any
form?: any
componentAs?: ReactSelect | CreatableSelect | AsyncSelect
}
function _Select<
Option,
IsMulti extends boolean = false,
Group extends GroupBase<Option> = GroupBase<Option>
function SelectBase<
Option,
IsMulti extends boolean = false,
Group extends GroupBase<Option> = GroupBase<Option>,
>(
props: SelectProps<Option, IsMulti, Group>,
ref: ForwardedRef<ReactSelect | CreatableSelect | AsyncSelect>
props: SelectProps<Option, IsMulti, Group>,
ref: ForwardedRef<ReactSelect | CreatableSelect | AsyncSelect>,
) {
const {
size,
style,
className,
form,
field,
components,
componentAs: Component = ReactSelect,
...rest
} = props
const {
size,
style,
className,
form,
field,
components,
componentAs: Component = ReactSelect,
...rest
} = props
const { themeColor, controlSize, primaryColorLevel, mode } = useConfig()
const formControlSize = useForm()?.size
const inputGroupSize = useInputGroup()?.size
const { themeColor, controlSize, primaryColorLevel, mode } = useConfig()
const formControlSize = useForm()?.size
const inputGroupSize = useInputGroup()?.size
const selectSize = size || inputGroupSize || formControlSize || controlSize
const selectSize = size || inputGroupSize || formControlSize || controlSize
const sizeMetrics = SELECT_SIZE_METRICS[selectSize]
const twColor = TW_COLORS as any
const twColor = TW_COLORS as any
let isInvalid = false
let isInvalid = false
if (!isEmpty(form)) {
const { touched, errors } = form
if (!isEmpty(form)) {
const { touched, errors } = form
const touchedField = get(touched, field.name)
const errorField = get(errors, field.name)
const touchedField = get(touched, field.name)
const errorField = get(errors, field.name)
isInvalid = touchedField && errorField
isInvalid = touchedField && errorField
}
const getBoxShadow = (state: ControlProps<Option, IsMulti, Group>) => {
const shadaowBase = '0 0 0 1px '
if (isInvalid) {
return shadaowBase + twColor.red['500']
}
const getBoxShadow = (state: ControlProps<Option, IsMulti, Group>) => {
const shadaowBase = '0 0 0 1px '
if (isInvalid) {
return shadaowBase + twColor.red['500']
}
if (state.isFocused) {
return shadaowBase + twColor[themeColor][primaryColorLevel]
}
return 'none'
if (state.isFocused) {
return shadaowBase + twColor[themeColor][primaryColorLevel]
}
const selectClass = classNames('select', `select-${selectSize}`, className)
return 'none'
}
return (
<Component<Option, IsMulti, Group>
ref={ref}
className={selectClass}
classNamePrefix={'select'}
styles={{
control: (provided, state) => {
// Tailwind spacing karşılığı: CONTROL_SIZES -> '10' gibi anahtarlar dönüyorsa SPACING ile eşleşir
const sizeKey = String(CONTROL_SIZES[selectSize]) as keyof typeof SPACING
const ctlHeight = (SPACING as any)[sizeKey] as string | undefined
const selectClass = classNames('select', `select-${selectSize}`, className)
return {
...provided,
...(ctlHeight ? { height: ctlHeight, minHeight: ctlHeight } : {}),
'&:hover': {
boxShadow: getBoxShadow(state),
cursor: 'pointer',
},
boxShadow: getBoxShadow(state),
borderRadius: BORDER_RADIUS.md,
...(isInvalid
? { borderColor: twColor.red['500'] }
: {}),
}
},
input: (css) => {
return {
...css,
input: {
outline: 'none',
outlineOffset: 0,
boxShadow: 'none !important',
},
}
},
menu: (provided) => ({ ...provided, zIndex: 50 }),
...style,
}}
theme={(theme) => ({
...theme,
colors: {
...theme.colors,
neutral0: mode === 'dark'
? twColor.gray['800']
: (TW_COLORS as any).white,
neutral20:
mode === 'dark'
? twColor.gray['600']
: twColor.gray['300'],
neutral30:
mode === 'dark'
? twColor.gray['600']
: twColor.gray['300'],
neutral80:
mode === 'dark'
? (TW_COLORS as any).white
: twColor.gray['700'],
neutral10:
mode === 'dark'
? twColor.gray['600']
: twColor.gray['300'],
primary25: twColor[themeColor]['50'],
primary50: twColor[themeColor]['100'],
primary: twColor[themeColor][primaryColorLevel],
},
})}
themeColor={`${themeColor}-${primaryColorLevel}`}
components={{
IndicatorSeparator: () => null,
Option: DefaultOption,
MenuList: DefaultMenuList,
LoadingIndicator: DefaultLoadingIndicator,
DropdownIndicator: DefaultDropdownIndicator,
ClearIndicator: DefaultClearIndicator,
...components,
}}
{...field}
{...rest}
/>
)
return (
<Component<Option, IsMulti, Group>
ref={ref}
className={selectClass}
classNamePrefix={'select'}
styles={{
control: (provided, state) => {
return {
...provided,
fontSize: sizeMetrics.fontSize,
height: sizeMetrics.height,
minHeight: sizeMetrics.height,
'&:hover': {
boxShadow: getBoxShadow(state),
cursor: 'pointer',
},
boxShadow: getBoxShadow(state),
borderRadius: BORDER_RADIUS.md,
...(isInvalid ? { borderColor: twColor.red['500'] } : {}),
}
},
input: (css) => {
return {
...css,
margin: 0,
paddingBottom: 0,
paddingTop: 0,
input: {
outline: 'none',
outlineOffset: 0,
boxShadow: 'none !important',
},
}
},
valueContainer: (provided) => ({
...provided,
height: sizeMetrics.height - 2,
padding: `0 ${sizeMetrics.horizontalPadding}px`,
}),
indicatorsContainer: (provided) => ({
...provided,
height: sizeMetrics.height - 2,
}),
placeholder: (provided) => ({ ...provided, margin: 0 }),
singleValue: (provided) => ({ ...provided, margin: 0 }),
menu: (provided) => ({ ...provided, zIndex: 50 }),
...style,
}}
theme={(theme) => ({
...theme,
colors: {
...theme.colors,
neutral0: mode === 'dark' ? twColor.gray['800'] : (TW_COLORS as any).white,
neutral20: mode === 'dark' ? twColor.gray['600'] : twColor.gray['300'],
neutral30: mode === 'dark' ? twColor.gray['600'] : twColor.gray['300'],
neutral80: mode === 'dark' ? (TW_COLORS as any).white : twColor.gray['700'],
neutral10: mode === 'dark' ? twColor.gray['600'] : twColor.gray['300'],
primary25: twColor[themeColor]['50'],
primary50: twColor[themeColor]['100'],
primary: twColor[themeColor][primaryColorLevel],
},
})}
themeColor={`${themeColor}-${primaryColorLevel}`}
components={{
IndicatorSeparator: () => null,
Option: DefaultOption,
MenuList: DefaultMenuList,
LoadingIndicator: DefaultLoadingIndicator,
DropdownIndicator: DefaultDropdownIndicator,
ClearIndicator: DefaultClearIndicator,
...components,
}}
{...field}
{...rest}
/>
)
}
const Select = forwardRef(_Select) as <
Option,
IsMulti extends boolean = false,
Group extends GroupBase<Option> = GroupBase<Option>
const Select = forwardRef(SelectBase) as <
Option,
IsMulti extends boolean = false,
Group extends GroupBase<Option> = GroupBase<Option>,
>(
props: SelectProps<Option, IsMulti, Group> & {
ref?: ForwardedRef<ReactSelect | CreatableSelect | AsyncSelect>
}
) => ReturnType<typeof _Select>
props: SelectProps<Option, IsMulti, Group> & {
ref?: ForwardedRef<ReactSelect | CreatableSelect | AsyncSelect>
},
) => ReturnType<typeof SelectBase>
export default Select

View file

@ -0,0 +1,892 @@
import React from 'react'
import * as UiKit from '@/components/ui'
import PlatformViewHost, {
type PlatformViewName,
} from '@/components/componentEditor/PlatformViewHost'
import { FaArrowDown, FaArrowUp, FaClone, FaGripVertical, FaTrash } from 'react-icons/fa'
import {
getDesignerValueByPath,
normalizeDesignerKeyList,
type DesignerBinding,
type DesignerNode,
} from './types'
export const DESIGNER_DRAG_TYPE = 'application/x-sozsoft-designer'
interface VisualCanvasProps {
nodes: DesignerNode[]
selectedId: string | null
background: string
interactive?: boolean
onSelect?: (id: string) => void
onDropComponent?: (definitionName: string, parentId: string | null) => void
onMove?: (id: string, direction: -1 | 1) => void
onDuplicate?: (id: string) => void
onDelete?: (id: string) => void
renderCustomComponent?: (name: string, props?: Record<string, unknown>) => React.ReactNode
dataValues?: Record<string, unknown>
}
class PreviewBoundary extends React.Component<
{ name: string; resetKey: string; children: React.ReactNode },
{ failed: boolean }
> {
state = { failed: false }
static getDerivedStateFromError() {
return { failed: true }
}
componentDidUpdate(previousProps: Readonly<{ resetKey: string }>) {
if (this.state.failed && previousProps.resetKey !== this.props.resetKey) {
this.setState({ failed: false })
}
}
render() {
if (this.state.failed) {
return (
<div className="rounded-md border border-amber-300 bg-amber-50 px-3 py-2 text-xs text-amber-800 dark:border-amber-800 dark:bg-amber-950 dark:text-amber-200">
{this.props.name} önizlemesi için ek veri veya alt bileşen gerekiyor.
</div>
)
}
return this.props.children
}
}
const resolveUiComponent = (name: string): React.ElementType | null => {
const parts = name.split('.')
let component: unknown = (UiKit as Record<string, unknown>)[parts[0]]
for (const part of parts.slice(1)) {
if ((typeof component !== 'object' && typeof component !== 'function') || !component) {
return null
}
component = (component as Record<string, unknown>)[part]
}
return component ? (component as React.ElementType) : null
}
const executeEvent = (script: string, event: unknown, node: DesignerNode) => {
if (!script.trim()) return
try {
const run = new Function('event', 'component', 'props', script)
run(event, node, node.props)
} catch (error) {
console.error(`Designer event error (${node.type}):`, error)
}
}
const getBindingValue = (
binding: DesignerBinding,
dataValues: Record<string, unknown>,
currentItem?: unknown,
) => {
const path = binding.path.trim()
if (currentItem !== undefined && (path === '$item' || path.startsWith('$item.'))) {
return getDesignerValueByPath(currentItem, path === '$item' ? '' : path.slice(6))
}
return getDesignerValueByPath(dataValues[binding.sourceId], path)
}
const toSelectOptions = (
value: unknown,
labelPath = '',
valuePath = '',
): Array<Record<string, unknown>> => {
if (!Array.isArray(value)) return []
return value.map((item, index) => {
if (item === null || typeof item !== 'object' || Array.isArray(item)) {
return { label: String(item ?? ''), value: item ?? index }
}
const record = item as Record<string, unknown>
const primitiveKeys = Object.keys(record).filter(
(key) => record[key] === null || ['string', 'number', 'boolean'].includes(typeof record[key]),
)
const labelKey = ['label', 'name', 'title', 'text', 'description'].find(
(key) => record[key] !== undefined,
)
const valueKey = ['value', 'id', 'code', 'key'].find((key) => record[key] !== undefined)
const labelValue = labelPath
? getDesignerValueByPath(record, labelPath)
: labelKey
? record[labelKey]
: record[primitiveKeys[0]]
const optionValue = valuePath
? getDesignerValueByPath(record, valuePath)
: valueKey
? record[valueKey]
: record[primitiveKeys[1] || primitiveKeys[0]]
return {
...record,
label: String(labelValue ?? `Seçenek ${index + 1}`),
value: optionValue ?? index,
}
})
}
const getPreviewProps = (
node: DesignerNode,
dataValues: Record<string, unknown>,
currentItem?: unknown,
) => {
const props: Record<string, unknown> = {}
Object.entries(node.props).forEach(([key, value]) => {
if (key === 'children' || key === 'html' || value === '') return
if (key.startsWith('on') && typeof value === 'string') return
props[key] = value
})
Object.entries(node.events).forEach(([name, script]) => {
if (!script.trim()) return
props[name] = (...args: unknown[]) => {
const event =
node.type === 'Checkbox' && name === 'onChange'
? {
checked: Boolean(args[0]),
originalEvent: args[1],
target:
args[1] && typeof args[1] === 'object' && 'target' in args[1]
? (args[1] as { target: unknown }).target
: undefined,
}
: args[0]
executeEvent(script, event, node)
}
})
Object.entries(node.bindings || {}).forEach(([propertyName, binding]) => {
if (propertyName !== 'children' && binding.sourceId) {
props[propertyName] = getBindingValue(binding, dataValues, currentItem)
}
})
if (node.type === 'Select' || node.type === 'AutoComplete' || node.type === 'Menu') {
const collectionProperty = node.type === 'Menu' ? 'items' : 'options'
if (node.type === 'Select') {
const legacyAliases: Record<string, string> = {
clearable: 'isClearable',
disabled: 'isDisabled',
multiple: 'isMulti',
searchable: 'isSearchable',
}
Object.entries(legacyAliases).forEach(([legacyName, runtimeName]) => {
if (props[runtimeName] === undefined && props[legacyName] !== undefined) {
props[runtimeName] = props[legacyName]
}
delete props[legacyName]
})
}
const optionsBinding = node.bindings?.[collectionProperty]
props[collectionProperty] = toSelectOptions(
props[collectionProperty],
optionsBinding?.labelPath,
optionsBinding?.valuePath,
)
if (node.type === 'Select' && 'value' in props) {
const options = props.options as Array<Record<string, unknown>>
props.defaultValue = options.find((option) => option.value === props.value) || null
delete props.value
}
}
if (node.type === 'Input' && 'value' in props) {
props.defaultValue = props.value
delete props.value
}
if (node.type === 'Checkbox' && 'checked' in props) {
props.defaultChecked = Boolean(props.checked)
delete props.checked
}
if ('value' in props && !props.onChange) props.onChange = () => undefined
if ('checked' in props && !props.onChange) props.onChange = () => undefined
props.key = JSON.stringify([node.props, node.bindings])
return props
}
const PlatformPlaceholder = ({ node }: { node: DesignerNode }) => (
<div
className="flex min-h-40 items-center justify-center rounded-lg border border-dashed border-sky-300 bg-gradient-to-br from-sky-50 to-indigo-50 p-6 text-center dark:border-sky-800 dark:from-slate-900 dark:to-sky-950"
style={{ minHeight: String(node.props.height || '320px') }}
>
<div>
<div className="mx-auto mb-3 flex h-10 w-10 items-center justify-center rounded-lg bg-sky-600 font-bold text-white">
S
</div>
<div className="font-semibold text-slate-800 dark:text-slate-100">
{node.type.replace(/View$/, '')}
</div>
<div className="mt-1 text-xs text-slate-500">
{String(node.props.listFormCode || 'Property panelinden List Form Code seçin')}
</div>
<div className="mt-3 text-[11px] uppercase tracking-wider text-sky-700 dark:text-sky-300">
Platform görünümü
</div>
</div>
</div>
)
const PLATFORM_VIEW_NAMES: Record<string, PlatformViewName> = {
ListView: 'List',
DataGridView: 'Grid',
TreeView: 'Tree',
GanttView: 'GanttView',
TodoBoard: 'TodoBoard',
CardView: 'CardView',
SchedulerView: 'SchedulerView',
PivotView: 'Pivot',
ChartView: 'Chart',
}
const GridColumnHeaders = ({ columns }: { columns: string[] }) => (
<div className="min-w-0 overflow-auto rounded-md border border-slate-200 bg-slate-100 [grid-column:1/-1] dark:border-slate-700 dark:bg-slate-800">
{columns.length ? (
<div className="flex min-w-max divide-x divide-slate-200 dark:divide-slate-700">
{columns.map((column) => (
<div
key={column}
className="min-w-32 px-3 py-2 text-xs font-semibold text-slate-600 dark:text-slate-200"
>
{column}
</div>
))}
</div>
) : (
<div className="px-3 py-2 text-center text-xs text-slate-400">
Data panelinden gösterilecek sütunları seçin.
</div>
)}
</div>
)
const getGridCellText = (value: unknown) =>
typeof value === 'object' && value !== null ? JSON.stringify(value) : String(value ?? '') || '—'
const GridDataTablePreview = ({
borderlessRow = false,
compact = false,
hoverable = true,
items,
overflow = true,
selectedColumns,
}: {
borderlessRow?: boolean
compact?: boolean
hoverable?: boolean
items: unknown[]
overflow?: boolean
selectedColumns?: string[]
}) => {
const firstObject = items.find(
(item): item is Record<string, unknown> =>
Boolean(item) && typeof item === 'object' && !Array.isArray(item),
)
const columns = selectedColumns ?? (firstObject ? Object.keys(firstObject) : ['value'])
if (!columns.length) {
return (
<div className="rounded-lg border border-dashed border-slate-300 bg-slate-50 p-4 text-center text-xs text-slate-500 [grid-column:1/-1] dark:border-slate-700 dark:bg-slate-900 dark:text-slate-400">
Preview için en az bir sütun seçin.
</div>
)
}
return (
<div
className={`min-w-0 rounded-lg border border-slate-200 bg-white shadow-sm [grid-column:1/-1] dark:border-slate-700 dark:bg-slate-900 ${overflow ? 'overflow-auto' : 'overflow-visible'}`}
>
<table className={`w-full border-collapse text-left ${compact ? 'text-[11px]' : 'text-xs'}`}>
<thead className="bg-slate-100 text-slate-600 dark:bg-slate-800 dark:text-slate-200">
<tr>
{columns.map((column) => (
<th
key={column}
className={`whitespace-nowrap border-b border-slate-200 dark:border-slate-700 ${compact ? 'px-2 py-1' : 'px-3 py-2'}`}
>
{column === 'value' ? 'Value' : column}
</th>
))}
</tr>
</thead>
<tbody>
{items.slice(0, 100).map((item, rowIndex) => (
<tr
key={rowIndex}
className={`${borderlessRow ? '' : 'border-b border-slate-100 last:border-b-0 dark:border-slate-800'} ${hoverable ? 'transition-colors hover:bg-slate-50 dark:hover:bg-slate-800/70' : ''}`}
>
{columns.map((column) => {
const value = column === 'value' ? item : getDesignerValueByPath(item, column)
const text = getGridCellText(value)
return (
<td
key={column}
className={`max-w-64 truncate text-slate-800 dark:text-slate-100 ${compact ? 'px-2 py-1' : 'px-3 py-2'}`}
title={text}
>
{text}
</td>
)
})}
</tr>
))}
</tbody>
</table>
</div>
)
}
const renderElement = (
node: DesignerNode,
children: React.ReactNode,
dataValues: Record<string, unknown>,
currentItem: unknown,
interactive: boolean,
renderCustomComponent?: (name: string, props?: Record<string, unknown>) => React.ReactNode,
) => {
if (node.type === 'Spacer') {
return <div aria-hidden="true" style={{ height: Number(node.props.height) || 24 }} />
}
if (node.type === 'FlexRow') {
return (
<div
className={String(node.props.className || '')}
style={{
display: 'flex',
gap: Number(node.props.gap) || 0,
flexWrap: node.props.wrap ? 'wrap' : 'nowrap',
alignItems: String(node.props.align || 'stretch'),
}}
>
{children}
</div>
)
}
if (node.type === 'PageContainer') {
return (
<div
className={String(node.props.className || '')}
style={{
display: 'flex',
flexDirection: 'column',
gap: Number(node.props.gap) || 0,
margin: '0 auto',
maxWidth: String(node.props.maxWidth || '1280px'),
padding: Number(node.props.padding) || 0,
width: '100%',
}}
>
{children}
</div>
)
}
if (node.type === 'TwoColumns' || node.type === 'SidebarContent') {
return (
<div
className={String(node.props.className || '')}
style={{
display: 'grid',
gap: Number(node.props.gap) || 0,
gridTemplateColumns:
node.type === 'SidebarContent'
? `${String(node.props.sidebarWidth || '280px')} minmax(0, 1fr)`
: 'repeat(2, minmax(0, 1fr))',
}}
>
{children}
</div>
)
}
if (node.type === 'HeaderContent') {
return (
<div
className={String(node.props.className || '')}
style={{ display: 'flex', flexDirection: 'column', gap: Number(node.props.gap) || 0 }}
>
{children}
</div>
)
}
if (node.type === 'Grid') {
return (
<div
className={String(node.props.className || '')}
style={{
display: 'grid',
gridTemplateColumns: `repeat(${Number(node.props.cols) || 3}, minmax(0, 1fr))`,
gap: (Number(node.props.gap) || 0) * 4,
}}
>
{children}
</div>
)
}
if (node.kind === 'platform') {
if (interactive) return <PlatformPlaceholder node={node} />
return (
<PlatformViewHost
height={String(node.props.height || '420px')}
listFormCode={String(node.props.listFormCode || '')}
view={PLATFORM_VIEW_NAMES[node.type] || (node.type as PlatformViewName)}
/>
)
}
if (node.kind === 'custom') {
return renderCustomComponent?.(node.type, { ...node.props, children }) || null
}
const childrenBinding = node.bindings?.children
const boundChildren = childrenBinding?.sourceId
? getBindingValue(childrenBinding, dataValues, currentItem)
: undefined
const content: React.ReactNode =
React.Children.count(children) > 0
? children
: boundChildren === null || boundChildren === undefined
? String(node.props.children ?? '') || undefined
: React.isValidElement(boundChildren)
? boundChildren
: typeof boundChildren === 'object'
? JSON.stringify(boundChildren)
: String(boundChildren)
const props = getPreviewProps(node, dataValues, currentItem)
if (node.type === 'Table') {
const legacyData = Array.isArray(props.data) ? props.data : []
const items =
Array.isArray(props.items) && (props.items.length > 0 || !legacyData.length)
? props.items
: legacyData
const legacyColumns = Array.isArray(node.props.columns)
? node.props.columns
.map((column) => {
if (typeof column === 'string') return column
if (!column || typeof column !== 'object' || Array.isArray(column)) return ''
const record = column as Record<string, unknown>
return String(record.dataIndex ?? record.key ?? '')
})
.filter(Boolean)
: undefined
return (
<GridDataTablePreview
borderlessRow={Boolean(node.props.borderlessRow)}
compact={Boolean(node.props.compact)}
hoverable={node.props.hoverable !== false}
items={items}
overflow={node.props.overflow !== false}
selectedColumns={
Array.isArray(node.props.dataColumns)
? node.props.dataColumns.filter(
(column): column is string => typeof column === 'string',
)
: legacyColumns
}
/>
)
}
if (node.type === 'Menu') {
const menuProps = { ...props }
const options = Array.isArray(menuProps.items)
? (menuProps.items as Array<Record<string, unknown>>)
: []
delete menuProps.items
delete menuProps.variant
menuProps.defaultActiveKeys = normalizeDesignerKeyList(menuProps.defaultActiveKeys)
menuProps.defaultExpandedKeys = normalizeDesignerKeyList(menuProps.defaultExpandedKeys)
return (
<UiKit.Menu {...(menuProps as React.ComponentProps<typeof UiKit.Menu>)}>
{options.map((option, index) => (
<UiKit.Menu.MenuItem
key={String(option.value ?? index)}
eventKey={String(option.value ?? index)}
>
{String(option.label ?? option.value ?? `Menü ${index + 1}`)}
</UiKit.Menu.MenuItem>
))}
{content}
</UiKit.Menu>
)
}
if (node.type === 'checkbox') {
return <input {...(props as React.InputHTMLAttributes<HTMLInputElement>)} type="checkbox" />
}
if (node.kind === 'html') return React.createElement(node.type, props, content)
const Component = resolveUiComponent(node.type)
if (!Component) {
return (
<div className="rounded-md border border-slate-200 bg-slate-50 p-3 text-sm text-slate-600 dark:border-slate-700 dark:bg-slate-800 dark:text-slate-200">
{node.type}
</div>
)
}
return React.createElement(Component, props, content)
}
const NodeView = ({
node,
index,
selectedId,
interactive,
onSelect,
onDropComponent,
onMove,
onDuplicate,
onDelete,
renderCustomComponent,
dataValues,
currentItem,
}: {
node: DesignerNode
index: number
selectedId: string | null
interactive: boolean
onSelect?: (id: string) => void
onDropComponent?: (definitionName: string, parentId: string | null) => void
onMove?: (id: string, direction: -1 | 1) => void
onDuplicate?: (id: string) => void
onDelete?: (id: string) => void
renderCustomComponent?: (name: string, props?: Record<string, unknown>) => React.ReactNode
dataValues: Record<string, unknown>
currentItem?: unknown
}) => {
const selected = interactive && selectedId === node.id
const acceptsDroppedChildren = [
'PageContainer',
'TwoColumns',
'SidebarContent',
'HeaderContent',
'FlexRow',
'div',
'Card',
'FormContainer',
].includes(node.type)
const itemsBinding = ['Grid', 'Table'].includes(node.type) ? node.bindings?.items : undefined
const boundItems = itemsBinding?.sourceId
? getBindingValue(itemsBinding, dataValues, currentItem)
: node.props.items
const sourceRootItems = itemsBinding?.sourceId ? dataValues[itemsBinding.sourceId] : undefined
const effectiveBoundItems =
Array.isArray(boundItems) || currentItem !== undefined
? boundItems
: Array.isArray(sourceRootItems)
? sourceRootItems
: boundItems
const repeatedItems = Array.isArray(effectiveBoundItems)
? interactive && !(node.type === 'Grid' && node.children.length === 0)
? effectiveBoundItems.slice(0, 1)
: effectiveBoundItems
: []
const childContexts = repeatedItems.length ? repeatedItems : [currentItem]
const children = childContexts.flatMap((childItem, itemIndex) =>
node.children.map((child, childIndex) => (
<NodeView
key={`${child.id}_${itemIndex}`}
node={child}
index={childIndex}
selectedId={selectedId}
interactive={interactive}
renderCustomComponent={renderCustomComponent}
dataValues={dataValues}
currentItem={childItem}
onSelect={onSelect}
onDropComponent={onDropComponent}
onMove={onMove}
onDuplicate={onDuplicate}
onDelete={onDelete}
/>
)),
)
const renderedChildren =
node.type === 'Grid' && interactive && node.children.length === 0
? repeatedItems.length
? [
<GridDataTablePreview
key={`grid_data_${node.id}`}
borderlessRow={Boolean(node.props.borderlessRow)}
compact={Boolean(node.props.compact)}
hoverable={node.props.hoverable !== false}
items={repeatedItems}
overflow={node.props.overflow !== false}
selectedColumns={
Array.isArray(node.props.dataColumns)
? node.props.dataColumns.filter(
(column): column is string => typeof column === 'string',
)
: undefined
}
/>,
]
: [
<GridColumnHeaders
key={`grid_headers_${node.id}`}
columns={
Array.isArray(node.props.dataColumns)
? node.props.dataColumns.filter(
(column): column is string => typeof column === 'string',
)
: []
}
/>,
]
: node.type === 'Grid' &&
!interactive &&
itemsBinding?.sourceId &&
effectiveBoundItems === undefined
? [
<div
key={`grid_loading_${node.id}`}
className="rounded-lg border border-slate-200 bg-slate-50 p-4 text-center text-xs text-slate-500 [grid-column:1/-1] dark:border-slate-700 dark:bg-slate-900 dark:text-slate-400"
>
Veriler yükleniyor
</div>,
]
: node.type === 'Grid' &&
itemsBinding?.sourceId &&
effectiveBoundItems !== undefined &&
!Array.isArray(effectiveBoundItems)
? [
<div
key={`grid_binding_error_${node.id}`}
className="rounded-lg border border-amber-300 bg-amber-50 p-3 text-xs text-amber-800 dark:border-amber-800 dark:bg-amber-950 dark:text-amber-200"
>
Grid items bağlantısı bir koleksiyon döndürmelidir. Seçili path:{' '}
<code>{itemsBinding.path || '(root)'}</code>
</div>,
]
: node.type === 'Grid' && node.children.length === 0 && repeatedItems.length
? [
<GridDataTablePreview
key={`grid_data_${node.id}`}
borderlessRow={Boolean(node.props.borderlessRow)}
compact={Boolean(node.props.compact)}
hoverable={node.props.hoverable !== false}
items={repeatedItems}
overflow={node.props.overflow !== false}
selectedColumns={
Array.isArray(node.props.dataColumns)
? node.props.dataColumns.filter(
(column): column is string => typeof column === 'string',
)
: undefined
}
/>,
]
: children
return (
<div
className={`group/node relative min-h-[28px] rounded-md ${
interactive
? selected
? 'outline outline-2 outline-sky-500 outline-offset-2'
: 'outline outline-1 outline-transparent hover:outline-sky-300'
: ''
}`}
data-designer-node={node.id}
draggable={interactive}
onClickCapture={() => {
if (interactive) onSelect?.(node.id)
}}
onClick={(event) => {
if (!interactive) return
event.stopPropagation()
onSelect?.(node.id)
}}
onDragStart={(event) => {
if (!interactive) return
event.stopPropagation()
event.dataTransfer.setData(
DESIGNER_DRAG_TYPE,
JSON.stringify({ source: 'canvas', nodeId: node.id }),
)
}}
onDragOver={(event) => {
if (!interactive || !acceptsDroppedChildren) return
event.preventDefault()
event.stopPropagation()
event.dataTransfer.dropEffect = 'copy'
}}
onDrop={(event) => {
if (!interactive || !acceptsDroppedChildren) return
event.preventDefault()
event.stopPropagation()
const raw =
event.dataTransfer.getData(DESIGNER_DRAG_TYPE) ||
event.dataTransfer.getData('text/plain')
if (!raw) return
const payload = JSON.parse(raw)
if (payload.source === 'library') onDropComponent?.(payload.name, node.id)
}}
>
{interactive && (
<div
className={`absolute -top-7 right-0 z-20 items-center overflow-hidden rounded-md bg-sky-600 text-white shadow-md ${
selected ? 'flex' : 'hidden group-hover/node:flex'
}`}
>
<span className="flex items-center gap-1 px-2 text-[10px] font-semibold">
<FaGripVertical /> {node.type}
</span>
<button
className="p-1.5 hover:bg-sky-700"
title="Yukarı taşı"
type="button"
onClick={(event) => {
event.stopPropagation()
onMove?.(node.id, -1)
}}
>
<FaArrowUp />
</button>
<button
className="p-1.5 hover:bg-sky-700"
title="Aşağı taşı"
type="button"
onClick={(event) => {
event.stopPropagation()
onMove?.(node.id, 1)
}}
>
<FaArrowDown />
</button>
<button
className="p-1.5 hover:bg-sky-700"
title="Çoğalt"
type="button"
onClick={(event) => {
event.stopPropagation()
onDuplicate?.(node.id)
}}
>
<FaClone />
</button>
<button
className="p-1.5 hover:bg-red-600"
title="Sil"
type="button"
onClick={(event) => {
event.stopPropagation()
onDelete?.(node.id)
}}
>
<FaTrash />
</button>
</div>
)}
<PreviewBoundary
name={node.type}
resetKey={JSON.stringify([node.props, node.bindings, node.events])}
>
{renderElement(
node,
renderedChildren,
dataValues,
currentItem,
interactive,
renderCustomComponent,
)}
</PreviewBoundary>
{interactive &&
node.children.length === 0 &&
acceptsDroppedChildren && (
<div className="m-2 rounded border border-dashed border-slate-300 px-3 py-4 text-center text-xs text-slate-400">
Bileşeni buraya bırakın
</div>
)}
<span className="hidden">{index}</span>
</div>
)
}
const VisualCanvas = ({
nodes,
selectedId,
background,
interactive = true,
onSelect,
onDropComponent,
onMove,
onDuplicate,
onDelete,
renderCustomComponent,
dataValues = {},
}: VisualCanvasProps) => {
const previewDataValues = dataValues
return (
<div
className="min-h-full p-8 text-slate-900 dark:text-slate-100"
style={{ background }}
onClick={() => interactive && onSelect?.('')}
onDragOver={(event) => interactive && event.preventDefault()}
onDrop={(event) => {
if (!interactive) return
event.preventDefault()
const raw =
event.dataTransfer.getData(DESIGNER_DRAG_TYPE) ||
event.dataTransfer.getData('text/plain')
if (!raw) return
const payload = JSON.parse(raw)
if (payload.source === 'library') onDropComponent?.(payload.name, null)
}}
>
{nodes.length ? (
<div className="space-y-4">
{nodes.map((node, index) => (
<NodeView
key={node.id}
node={node}
index={index}
selectedId={selectedId}
interactive={interactive}
renderCustomComponent={renderCustomComponent}
dataValues={previewDataValues}
onSelect={onSelect}
onDropComponent={onDropComponent}
onMove={onMove}
onDuplicate={onDuplicate}
onDelete={onDelete}
/>
))}
</div>
) : (
<div className="flex min-h-[520px] items-center justify-center rounded-xl border-2 border-dashed border-slate-300 bg-white/70 text-center dark:border-slate-700 dark:bg-slate-900/70">
<div className="w-full max-w-2xl px-6">
<div className="mx-auto mb-4 flex h-14 w-14 items-center justify-center rounded-2xl bg-sky-100 text-2xl text-sky-600">
+
</div>
<h3 className="font-semibold text-slate-700 dark:text-slate-200">
Sayfa layoutunu seçin
</h3>
<p className="mt-1 text-sm text-slate-500">
Hazır bir yerleşimle başlayın veya Toolboxtaki My Components grubundan kendi layout
komponentinizi kullanın.
</p>
{interactive && (
<div className="mt-5 grid grid-cols-2 gap-2 sm:grid-cols-4">
{[
['PageContainer', 'Sayfa alanı', '□'],
['TwoColumns', 'İki kolon', '▥'],
['SidebarContent', 'Sidebar + içerik', '◧'],
['HeaderContent', 'Üst alan + içerik', '⊟'],
].map(([name, label, icon]) => (
<button
key={name}
className="rounded-lg border border-slate-200 bg-white p-3 text-xs font-semibold text-slate-600 shadow-sm transition hover:border-sky-400 hover:text-sky-700 dark:border-slate-700 dark:bg-slate-900 dark:text-slate-300"
type="button"
onClick={(event) => {
event.stopPropagation()
onDropComponent?.(name, null)
}}
>
<span className="mb-1 block text-xl text-sky-500">{icon}</span>
{label}
</button>
))}
</div>
)}
</div>
</div>
)}
</div>
)
}
export default VisualCanvas

View file

@ -0,0 +1,475 @@
import { CUSTOM_COMPONENTS, HTML_ELEMENTS } from '@/components/codeLayout/data/componentDefinitions'
import generatedComponentProps from './generated/componentProps.json'
import type { DesignerComponentDefinition, DesignerNodeKind } from './types'
interface GeneratedProperty {
name: string
tsType: string
type: 'string' | 'boolean' | 'number' | 'function' | 'object' | 'array' | 'select'
required: boolean
options?: string[]
defaultValue?: unknown
}
interface GeneratedComponentMetadata {
interfaceName: string
sourceFile: string
properties: GeneratedProperty[]
}
const generatedMetadata = generatedComponentProps as Record<string, GeneratedComponentMetadata>
const CONTAINER_NAMES = new Set(['div', 'Card', 'FormContainer', 'Tabs', 'Timeline'])
const DATA_COMPONENT_NAMES = new Set(['AutoComplete', 'Grid', 'Menu', 'Pagination', 'Select', 'Table'])
const HTML_UI_DUPLICATES = new Set(['input', 'button', 'textarea', 'select', 'checkbox'])
const UI_PROPERTY_ALIASES: Record<string, Record<string, string>> = {
Select: {
clearable: 'isClearable',
disabled: 'isDisabled',
multiple: 'isMulti',
onSearch: 'onInputChange',
searchable: 'isSearchable',
},
}
export const DESIGNER_PRIMARY_EVENTS: Record<string, string[]> = {
Button: ['onClick'],
Checkbox: ['onChange'],
Input: ['onChange'],
Menu: ['onSelect'],
Select: ['onChange'],
}
const DESIGNER_EVENT_SNIPPETS: Record<string, Record<string, string>> = {
Button: {
onClick: `// Butona tıklandığında çalışır.
console.log('Button clicked', event)`,
},
Checkbox: {
onChange: `// Checkbox bileşeninin yeni checked değeri.
const checked = event.checked
console.log('Checked:', checked)`,
},
Input: {
onChange: `// Input alanının güncel metin değeri.
const value = event.target.value
console.log('Value:', value)`,
},
Menu: {
onSelect: `// Seçilen menü öğesinin value değeridir.
console.log('Menu value:', event)`,
},
Select: {
onChange: `// Seçilen option nesnesi; temizlendiğinde null olur.
const selectedValue = event?.value ?? null
const selectedLabel = event?.label ?? ''
console.log({ selectedValue, selectedLabel })`,
},
}
export const getDesignerEventSnippet = (componentName: string, eventName: string) =>
DESIGNER_EVENT_SNIPPETS[componentName]?.[eventName] ||
`// ${componentName}.${eventName}(event)\nconsole.log(event)`
const COLLECTION_ITEMS_PROPERTY = {
name: 'items',
type: 'array' as const,
value: [],
category: 'properties' as const,
description: 'Statik veya endpoint kaynaklı veri koleksiyonu',
tsType: 'unknown[]',
options: undefined,
required: false,
}
const GRID_COMPACT_PROPERTY = {
name: 'compact',
type: 'boolean' as const,
value: false,
category: 'properties' as const,
description: 'Satır ve hücre boşluklarını azaltır',
tsType: 'boolean',
options: undefined,
required: false,
}
const GRID_TABLE_BEHAVIOR_PROPERTIES = [
{
name: 'hoverable',
type: 'boolean' as const,
value: true,
category: 'properties' as const,
description: 'Satırların üzerine gelindiğinde vurgu rengi gösterir',
tsType: 'boolean',
options: undefined,
required: false,
},
{
name: 'overflow',
type: 'boolean' as const,
value: true,
category: 'properties' as const,
description: 'Geniş tablolarda yatay kaydırmayı etkinleştirir',
tsType: 'boolean',
options: undefined,
required: false,
},
{
name: 'borderlessRow',
type: 'boolean' as const,
value: false,
category: 'properties' as const,
description: 'Veri satırları arasındaki kenarlıkları kaldırır',
tsType: 'boolean',
options: undefined,
required: false,
},
]
const SELECT_OPTIONS_PROPERTY = {
name: 'options',
type: 'array' as const,
value: [
{ label: 'Seçenek 1', value: '1' },
{ label: 'Seçenek 2', value: '2' },
],
category: 'properties' as const,
description: 'Select items; statik olarak veya endpoint koleksiyonundan beslenebilir',
tsType: 'Array<{ label: string; value: unknown }>',
options: undefined,
required: false,
}
const MENU_ITEMS_PROPERTY = {
...SELECT_OPTIONS_PROPERTY,
name: 'items',
description: 'Menu items; statik olarak veya endpoint koleksiyonundan beslenebilir',
}
const normalizeDefinition = (
definition: (typeof CUSTOM_COMPONENTS)[number],
kind: DesignerNodeKind,
toolboxGroup: DesignerComponentDefinition['toolboxGroup'],
): DesignerComponentDefinition => {
const sourceProperties =
definition.name === 'Grid'
? definition.properties.filter((property) => property.name !== 'children')
: definition.properties
let properties =
['Grid', 'Table'].includes(definition.name) &&
!sourceProperties.some((property) => property.name === 'items')
? [COLLECTION_ITEMS_PROPERTY, ...sourceProperties]
: sourceProperties
if (definition.name === 'Grid' && !properties.some((property) => property.name === 'compact')) {
properties = [GRID_COMPACT_PROPERTY, ...properties]
}
if (definition.name === 'Grid') {
properties = [
...GRID_TABLE_BEHAVIOR_PROPERTIES.filter(
(candidate) => !properties.some((property) => property.name === candidate.name),
),
...properties,
]
}
return {
...definition,
properties,
kind,
toolboxGroup,
acceptsChildren: CONTAINER_NAMES.has(definition.name),
}
}
const fallbackValue = (type: GeneratedProperty['type']) => {
if (type === 'boolean') return false
if (type === 'number') return 0
if (type === 'array') return []
if (type === 'object') return {}
return ''
}
const inferUiCategory = (name: string) => {
if (
new Set([
'AutoComplete',
'Checkbox',
'Chips',
'ColorPicker',
'DatePicker',
'Input',
'Knob',
'Radio',
'Rate',
'Select',
'Slider',
'Switcher',
'TimeInput',
'Upload',
]).has(name)
)
return 'form'
if (new Set(['Alert', 'Notification', 'Progress', 'Skeleton', 'Spinner']).has(name))
return 'feedback'
if (new Set(['Breadcrumb', 'Dropdown', 'Menu', 'Pagination', 'Steps', 'Tabs']).has(name))
return 'navigation'
return 'display'
}
const generatedPropertiesToDesigner = (
generated: GeneratedComponentMetadata,
existingProperties = new Map<string, (typeof CUSTOM_COMPONENTS)[number]['properties'][number]>(),
) =>
generated.properties.map((property) => {
const existing = existingProperties.get(property.name)
return {
name: property.name,
type: property.type,
value:
property.defaultValue !== undefined
? property.defaultValue
: existing?.value !== undefined
? existing.value
: fallbackValue(property.type),
options: property.options || existing?.options,
description: property.tsType,
category:
property.type === 'function'
? ('events' as const)
: existing?.category ||
(property.name === 'className' || property.name === 'style'
? ('styling' as const)
: ('properties' as const)),
tsType: property.tsType,
required: property.required,
}
})
const normalizeUiDefinition = (
definition: (typeof CUSTOM_COMPONENTS)[number],
): DesignerComponentDefinition => {
const generated = generatedMetadata[definition.name]
if (!generated) {
return normalizeDefinition(
definition,
'ui',
DATA_COMPONENT_NAMES.has(definition.name) ? 'data' : 'ui',
)
}
const propertyAliases = UI_PROPERTY_ALIASES[definition.name] || {}
const curatedProperties = definition.properties
.filter(
(property) =>
(definition.name !== 'Select' || property.name !== 'items') &&
(definition.name !== 'Menu' || property.name !== 'variant') &&
(definition.name !== 'Table' || !['columns', 'data'].includes(property.name)),
)
.map((property) => ({
...property,
name: propertyAliases[property.name] || property.name,
}))
const existingProperties = new Map(curatedProperties.map((property) => [property.name, property]))
const generatedProperties = generatedPropertiesToDesigner(generated, existingProperties)
if (
['Select', 'AutoComplete', 'Menu'].includes(definition.name) &&
!generatedProperties.some(
(property) => property.name === (definition.name === 'Menu' ? 'items' : 'options'),
)
) {
generatedProperties.unshift(
definition.name === 'Menu' ? MENU_ITEMS_PROPERTY : SELECT_OPTIONS_PROPERTY,
)
}
if (
['Grid', 'Table'].includes(definition.name) &&
!generatedProperties.some((property) => property.name === 'items')
) {
generatedProperties.unshift(COLLECTION_ITEMS_PROPERTY)
}
const generatedPropertyNames = new Set(generatedProperties.map((property) => property.name))
const properties = [
...generatedProperties,
// Keep explicitly curated inherited React props such as Input.value and placeholder.
...curatedProperties.filter((property) => !generatedPropertyNames.has(property.name)),
].filter(
(property) =>
!(definition.name === 'Checkbox' && property.name === 'defaultChecked') &&
!(definition.name === 'Menu' && property.name === 'variant'),
)
const primaryEvents = DESIGNER_PRIMARY_EVENTS[definition.name]
return {
...definition,
kind: 'ui',
toolboxGroup: DATA_COMPONENT_NAMES.has(definition.name) ? 'data' : 'ui',
acceptsChildren: CONTAINER_NAMES.has(definition.name),
sourceFile: generated.sourceFile,
interfaceName: generated.interfaceName,
properties: primaryEvents
? properties.filter(
(property) =>
(property.category !== 'events' && property.type !== 'function') ||
primaryEvents.includes(property.name),
)
: properties,
}
}
const platformDefinition = (
name: string,
sourceName: string,
description: string,
height = '520px',
): DesignerComponentDefinition => ({
name,
sourceName,
icon: 'PanelsTopLeft',
category: 'platform',
kind: 'platform',
toolboxGroup: 'platform',
description,
properties: [
{ name: 'listFormCode', type: 'string', value: '', category: 'properties', required: true },
{ name: 'height', type: 'string', value: height, category: 'styling' },
],
hooks: [],
})
export const PLATFORM_COMPONENTS: DesignerComponentDefinition[] = [
platformDefinition(
'ListView',
'List',
'Tüm etkin platform görünümlerini /admin/list/:listFormCode ile aynı şekilde yönetir',
),
platformDefinition('DataGridView', 'Grid', 'Sozsoft dinamik veri grid görünümü'),
platformDefinition('TreeView', 'Tree', 'Sozsoft dinamik ağaç görünümü'),
platformDefinition('GanttView', 'GanttView', 'Sozsoft dinamik Gantt görünümü'),
platformDefinition('TodoBoard', 'TodoBoard', 'Sozsoft dinamik iş panosu'),
platformDefinition('CardView', 'CardView', 'Sozsoft dinamik kart görünümü'),
platformDefinition('SchedulerView', 'SchedulerView', 'Sozsoft dinamik planlayıcı görünümü'),
platformDefinition('PivotView', 'Pivot', 'Sozsoft dinamik pivot görünümü'),
platformDefinition('ChartView', 'Chart', 'Sozsoft dinamik grafik görünümü', '420px'),
]
export const DESIGNER_EXTRAS: DesignerComponentDefinition[] = [
{
name: 'PageContainer',
icon: 'PanelTop',
category: 'layout',
kind: 'layout',
toolboxGroup: 'layout',
description: 'Intranet içeriği için ortalanmış sayfa alanı',
acceptsChildren: true,
properties: [
{ name: 'maxWidth', type: 'string', value: '1280px', category: 'styling' },
{ name: 'padding', type: 'number', value: 24, category: 'styling' },
{ name: 'gap', type: 'number', value: 16, category: 'styling' },
{ name: 'className', type: 'string', value: '', category: 'styling' },
],
hooks: [],
},
{
name: 'TwoColumns',
icon: 'Columns2',
category: 'layout',
kind: 'layout',
toolboxGroup: 'layout',
description: 'Sayfayı iki eşit kolona böler',
acceptsChildren: true,
properties: [
{ name: 'gap', type: 'number', value: 16, category: 'styling' },
{ name: 'className', type: 'string', value: '', category: 'styling' },
],
hooks: [],
},
{
name: 'SidebarContent',
icon: 'PanelLeft',
category: 'layout',
kind: 'layout',
toolboxGroup: 'layout',
description: 'Dar sidebar ve geniş içerik alanı',
acceptsChildren: true,
properties: [
{ name: 'sidebarWidth', type: 'string', value: '280px', category: 'styling' },
{ name: 'gap', type: 'number', value: 16, category: 'styling' },
{ name: 'className', type: 'string', value: '', category: 'styling' },
],
hooks: [],
},
{
name: 'HeaderContent',
icon: 'PanelTop',
category: 'layout',
kind: 'layout',
toolboxGroup: 'layout',
description: 'Üst alan ve alt içerik için dikey yerleşim',
acceptsChildren: true,
properties: [
{ name: 'gap', type: 'number', value: 16, category: 'styling' },
{ name: 'className', type: 'string', value: '', category: 'styling' },
],
hooks: [],
},
{
name: 'FlexRow',
icon: 'Columns3',
category: 'layout',
kind: 'layout',
toolboxGroup: 'layout',
description: 'Yatay ve responsive yerleşim',
acceptsChildren: true,
properties: [
{ name: 'gap', type: 'number', value: 16, category: 'styling' },
{ name: 'wrap', type: 'boolean', value: true, category: 'styling' },
{
name: 'align',
type: 'select',
value: 'stretch',
options: ['stretch', 'start', 'center', 'end'],
category: 'styling',
},
{ name: 'className', type: 'string', value: '', category: 'styling' },
],
hooks: [],
},
{
name: 'Spacer',
icon: 'Space',
category: 'layout',
kind: 'layout',
toolboxGroup: 'layout',
description: 'Bileşenler arasında boşluk',
properties: [{ name: 'height', type: 'number', value: 24, category: 'styling' }],
hooks: [],
},
]
export const getDesignerCatalog = (customNames: string[] = []): DesignerComponentDefinition[] => {
const html = HTML_ELEMENTS.filter((definition) => !HTML_UI_DUPLICATES.has(definition.name)).map(
(definition) => normalizeDefinition(definition, 'html', 'html'),
)
const ui = CUSTOM_COMPONENTS.map(normalizeUiDefinition)
const definedUiNames = new Set(CUSTOM_COMPONENTS.map((definition) => definition.name))
const generatedOnlyUi: DesignerComponentDefinition[] = Object.entries(generatedMetadata)
.filter(([name]) => !definedUiNames.has(name))
.map(([name, metadata]) => ({
name,
icon: 'Blocks',
category: inferUiCategory(name),
kind: 'ui',
toolboxGroup: DATA_COMPONENT_NAMES.has(name) ? 'data' : 'ui',
description: `${metadata.interfaceName} sözleşmesini kullanan Sozsoft UI componenti`,
sourceFile: metadata.sourceFile,
interfaceName: metadata.interfaceName,
acceptsChildren: CONTAINER_NAMES.has(name),
properties: generatedPropertiesToDesigner(metadata),
hooks: [],
}))
const custom: DesignerComponentDefinition[] = customNames.map((name) => ({
name,
icon: 'Blocks',
category: 'custom',
kind: 'custom',
toolboxGroup: 'custom',
description: 'Veritabanındaki yeniden kullanılabilir bileşen',
acceptsChildren: true,
properties: [{ name: 'className', type: 'string', value: '', category: 'styling' }],
hooks: [],
}))
return [...DESIGNER_EXTRAS, ...PLATFORM_COMPONENTS, ...html, ...ui, ...generatedOnlyUi, ...custom]
}

View file

@ -0,0 +1,415 @@
import { normalizeDesignerKeyList, type DesignerDocument, type DesignerNode } from './types'
const EVENT_NAMES: Record<string, string> = {
onClick: 'event',
onChange: 'event',
onFocus: 'event',
onBlur: 'event',
onMouseEnter: 'event',
onMouseLeave: 'event',
}
const safeIdentifier = (value: string) => {
const cleaned = value.replace(/[^A-Za-z0-9_$]/g, '_')
return /^[A-Za-z_$]/.test(cleaned) ? cleaned : `Component_${cleaned}`
}
const serializeValue = (value: unknown) => {
if (typeof value === 'string') return `{${JSON.stringify(value)}}`
if (typeof value === 'number' || typeof value === 'boolean') return `{${String(value)}}`
if (value === null) return '{null}'
if (Array.isArray(value) || typeof value === 'object') return `{${JSON.stringify(value)}}`
return JSON.stringify(String(value ?? ''))
}
const bindingExpression = (node: DesignerNode, propertyName: string, itemVariable?: string) => {
const binding = node.bindings?.[propertyName]
if (!binding?.sourceId) return ''
const path = binding.path.trim()
if (itemVariable && (path === '$item' || path.startsWith('$item.'))) {
return `getByPath(${itemVariable}, ${JSON.stringify(path === '$item' ? '' : path.slice(6))})`
}
return `getByPath(data_${safeIdentifier(binding.sourceId)}, ${JSON.stringify(binding.path || '')})`
}
const propsToCode = (node: DesignerNode, itemVariable?: string) => {
const hasBoundInputValue = node.type === 'Input' && Boolean(node.bindings?.value?.sourceId)
const hasInputChangeHandler = Boolean(node.events?.onChange?.trim())
const props = Object.entries(node.props)
.filter(
([key, value]) =>
key !== 'children' &&
!(node.type === 'Checkbox' && key === 'defaultChecked' && 'checked' in node.props) &&
!(hasBoundInputValue && !hasInputChangeHandler && key === 'readOnly') &&
!node.bindings?.[key]?.sourceId &&
value !== '' &&
value !== undefined,
)
.map(([key, value]) => {
if (node.type === 'Checkbox' && key === 'checked') {
return `defaultChecked=${serializeValue(value)}`
}
if (node.type === 'Input' && key === 'value') {
return `defaultValue=${serializeValue(value)}`
}
if (node.type === 'Select' && key === 'value') {
const staticOptions = Array.isArray(node.props.options) ? node.props.options : []
const selectedOption = staticOptions.find(
(option) =>
option &&
typeof option === 'object' &&
!Array.isArray(option) &&
(option as Record<string, unknown>).value === value,
)
return `defaultValue=${serializeValue(selectedOption ?? null)}`
}
return `${key}=${serializeValue(value)}`
})
Object.keys(node.bindings || {}).forEach((propertyName) => {
if (propertyName === 'children') return
const expression = bindingExpression(node, propertyName, itemVariable)
if (expression) {
const propertyExpression =
['Select', 'AutoComplete'].includes(node.type) && propertyName === 'options'
? `toSelectOptions(${expression}, ${JSON.stringify(node.bindings.options?.labelPath || '')}, ${JSON.stringify(node.bindings.options?.valuePath || '')})`
: expression
props.push(`${propertyName}={${propertyExpression}}`)
}
})
if (hasBoundInputValue && !hasInputChangeHandler) props.push('readOnly={true}')
Object.entries(node.events)
.filter(([, script]) => script.trim())
.forEach(([eventName]) => {
props.push(`${eventName}={handle_${safeIdentifier(node.id)}_${eventName}}`)
})
return props.length ? ` ${props.join(' ')}` : ''
}
const indent = (text: string, level: number) =>
text
.split('\n')
.map((line) => `${' '.repeat(level)}${line}`)
.join('\n')
const nodeToCode = (node: DesignerNode, level = 0, itemVariable?: string): string => {
if (node.type === 'Spacer') {
return indent(
`<div aria-hidden="true" style={{ height: ${Number(node.props.height) || 24} }} />`,
level,
)
}
if (node.type === 'FlexRow') {
const style = `{ display: "flex", gap: ${Number(node.props.gap) || 0}, flexWrap: "${node.props.wrap ? 'wrap' : 'nowrap'}", alignItems: "${String(node.props.align || 'stretch')}" }`
const className = node.props.className
? ` className=${JSON.stringify(node.props.className)}`
: ''
const children = node.children
.map((child) => nodeToCode(child, level + 1, itemVariable))
.join('\n')
return `${indent(`<div${className} style={${style}}>`, level)}\n${children}\n${indent('</div>', level)}`
}
if (
node.type === 'PageContainer' ||
node.type === 'TwoColumns' ||
node.type === 'SidebarContent' ||
node.type === 'HeaderContent'
) {
const gap = Number(node.props.gap) || 0
const className = JSON.stringify(String(node.props.className || ''))
const style =
node.type === 'PageContainer'
? `{ display: "flex", flexDirection: "column", gap: ${gap}, margin: "0 auto", maxWidth: ${JSON.stringify(String(node.props.maxWidth || '1280px'))}, padding: ${Number(node.props.padding) || 0}, width: "100%" }`
: node.type === 'HeaderContent'
? `{ display: "flex", flexDirection: "column", gap: ${gap} }`
: `{ display: "grid", gap: ${gap}, gridTemplateColumns: ${JSON.stringify(node.type === 'SidebarContent' ? `${String(node.props.sidebarWidth || '280px')} minmax(0, 1fr)` : 'repeat(2, minmax(0, 1fr))')} }`
const children = node.children
.map((child) => nodeToCode(child, level + 1, itemVariable))
.join('\n')
return `${indent(`<div className=${className} style={${style}}>`, level)}\n${children}\n${indent('</div>', level)}`
}
if (node.type === 'Table') {
const legacyData = Array.isArray(node.props.data) ? node.props.data : []
const staticItems =
Array.isArray(node.props.items) && (node.props.items.length > 0 || !legacyData.length)
? node.props.items
: legacyData
const itemsExpression =
bindingExpression(node, 'items', itemVariable) ||
bindingExpression(node, 'data', itemVariable) ||
JSON.stringify(staticItems)
const identifier = safeIdentifier(node.id)
const itemsVariable = `tableItems_${identifier}`
const columnsVariable = `tableColumns_${identifier}`
const rowVariable = `tableRow_${identifier}`
const configuredColumns = Array.isArray(node.props.dataColumns)
? node.props.dataColumns.filter((column): column is string => typeof column === 'string')
: Array.isArray(node.props.columns)
? node.props.columns
.map((column) => {
if (typeof column === 'string') return column
if (!column || typeof column !== 'object' || Array.isArray(column)) return ''
const record = column as Record<string, unknown>
return String(record.dataIndex ?? record.key ?? '')
})
.filter(Boolean)
: null
const columnsExpression = configuredColumns
? JSON.stringify(configuredColumns)
: `${itemsVariable}[0] && typeof ${itemsVariable}[0] === "object" && !Array.isArray(${itemsVariable}[0]) ? Object.keys(${itemsVariable}[0]) : ["value"]`
return `${indent('{(() => {', level)}
${indent(`const ${itemsVariable} = ${itemsExpression}`, level + 1)}
${indent(`if (!Array.isArray(${itemsVariable})) return <div className="rounded-lg border border-amber-300 bg-amber-50 p-3 text-xs text-amber-800 dark:border-amber-800 dark:bg-amber-950 dark:text-amber-200">Table items bağlantısı bir koleksiyon döndürmelidir.</div>`, level + 1)}
${indent(`const ${columnsVariable} = ${columnsExpression}`, level + 1)}
${indent('return (', level + 1)}
${indent(`<UiKit.Table borderlessRow={${Boolean(node.props.borderlessRow)}} compact={${Boolean(node.props.compact)}} hoverable={${node.props.hoverable !== false}} overflow={${node.props.overflow !== false}}>`, level + 2)}
${indent(`<thead><tr>{${columnsVariable}.map((column) => <th key={column}>{column === "value" ? "Value" : column}</th>)}</tr></thead>`, level + 3)}
${indent(`<tbody>{${itemsVariable}.map((${rowVariable}, rowIndex) => <tr key={rowIndex}>{${columnsVariable}.map((column) => { const value = column === "value" ? ${rowVariable} : getByPath(${rowVariable}, column); return <td key={column}>{typeof value === "object" && value !== null ? JSON.stringify(value) : String(value ?? "")}</td> })}</tr>)}</tbody>`, level + 3)}
${indent('</UiKit.Table>', level + 2)}
${indent(')', level + 1)}
${indent('})()}', level)}`
}
if (node.type === 'Grid') {
const columns = Number(node.props.cols) || 3
const gap = (Number(node.props.gap) || 0) * 4
const itemsExpression =
bindingExpression(node, 'items', itemVariable) ||
(Array.isArray(node.props.items) ? JSON.stringify(node.props.items) : '')
const itemsBinding = node.bindings?.items
const sourceRootExpression =
!itemVariable && itemsBinding?.sourceId ? `data_${safeIdentifier(itemsBinding.sourceId)}` : ''
const collectionExpression =
sourceRootExpression && sourceRootExpression !== itemsExpression
? `(Array.isArray(${itemsExpression}) ? ${itemsExpression} : Array.isArray(${sourceRootExpression}) ? ${sourceRootExpression} : null)`
: itemsExpression
const gridClassName = JSON.stringify(String(node.props.className || ''))
const gridStart = `<div className=${gridClassName} style={{ display: "grid", gridTemplateColumns: "repeat(${columns}, minmax(0, 1fr))", gap: ${gap} }}>`
if (collectionExpression && node.children.length === 0) {
const identifier = safeIdentifier(node.id)
const itemsVariable = `gridItems_${identifier}`
const columnsVariable = `gridColumns_${identifier}`
const itemVariable = `gridItem_${identifier}`
const valueVariable = `cellValue_${identifier}`
const configuredColumns = Array.isArray(node.props.dataColumns)
? node.props.dataColumns.filter((column): column is string => typeof column === 'string')
: null
const columnsExpression = configuredColumns
? JSON.stringify(configuredColumns)
: `${itemsVariable}[0] && typeof ${itemsVariable}[0] === "object" && !Array.isArray(${itemsVariable}[0]) ? Object.keys(${itemsVariable}[0]) : ["value"]`
const compact = Boolean(node.props.compact)
const borderlessRow = Boolean(node.props.borderlessRow)
const hoverable = node.props.hoverable !== false
const overflow = node.props.overflow !== false
const tableTextClass = compact ? 'text-[11px]' : 'text-xs'
const cellSpacingClass = compact ? 'px-2 py-1' : 'px-3 py-2'
const rowClass = [
borderlessRow ? '' : 'border-b border-slate-100 last:border-b-0 dark:border-slate-800',
hoverable ? 'transition-colors hover:bg-slate-50 dark:hover:bg-slate-800/70' : '',
]
.filter(Boolean)
.join(' ')
const overflowClass = overflow ? 'overflow-auto' : 'overflow-visible'
const table = `${indent('{(() => {', level + 1)}
${indent(`const ${itemsVariable} = ${collectionExpression}`, level + 2)}
${indent(`if (!Array.isArray(${itemsVariable})) return <div className="rounded-lg border border-amber-300 bg-amber-50 p-3 text-xs text-amber-800 dark:border-amber-800 dark:bg-amber-950 dark:text-amber-200">Grid items bağlantısı bir koleksiyon döndürmelidir.</div>`, level + 2)}
${indent(`const ${columnsVariable} = ${columnsExpression}`, level + 2)}
${indent(`if (!${columnsVariable}.length) return <div className="rounded-lg border border-dashed border-slate-300 bg-slate-50 p-4 text-center text-xs text-slate-500 dark:border-slate-700 dark:bg-slate-900 dark:text-slate-400">Preview için en az bir sütun seçin.</div>`, level + 2)}
${indent('return (', level + 2)}
${indent(`<div className="min-w-0 ${overflowClass} rounded-lg border border-slate-200 bg-white shadow-sm dark:border-slate-700 dark:bg-slate-900" style={{ gridColumn: "1 / -1" }}>`, level + 3)}
${indent(`<table className="w-full border-collapse text-left ${tableTextClass}">`, level + 4)}
${indent('<thead className="bg-slate-100 text-slate-600 dark:bg-slate-800 dark:text-slate-200"><tr>', level + 5)}
${indent(`{${columnsVariable}.map((column) => <th key={column} className="whitespace-nowrap border-b border-slate-200 dark:border-slate-700 ${cellSpacingClass}">{column === "value" ? "Value" : column}</th>)}`, level + 6)}
${indent('</tr></thead>', level + 5)}
${indent('<tbody>', level + 5)}
${indent(`{${itemsVariable}.map((${itemVariable}, rowIndex) => (`, level + 6)}
${indent(`<tr key={rowIndex} className=${JSON.stringify(rowClass)}>`, level + 7)}
${indent(`{${columnsVariable}.map((column) => {`, level + 8)}
${indent(`const ${valueVariable} = column === "value" ? ${itemVariable} : getByPath(${itemVariable}, column)`, level + 9)}
${indent(`const text = typeof ${valueVariable} === "object" && ${valueVariable} !== null ? JSON.stringify(${valueVariable}) : String(${valueVariable} ?? "") || "—"`, level + 9)}
${indent(`return <td key={column} className="max-w-64 truncate text-slate-800 dark:text-slate-100 ${cellSpacingClass}" title={text}>{text}</td>`, level + 9)}
${indent('})}', level + 8)}
${indent('</tr>', level + 7)}
${indent('))}', level + 6)}
${indent('</tbody>', level + 5)}
${indent('</table>', level + 4)}
${indent('</div>', level + 3)}
${indent(')', level + 2)}
${indent('})()}', level + 1)}`
return `${indent(gridStart, level)}\n${table}\n${indent('</div>', level)}`
}
const repeatedItemVariable = `item_${safeIdentifier(node.id)}`
const staticChildren = node.children
.map((child) => nodeToCode(child, level + 1, itemVariable))
.join('\n')
const repeatedChildren = node.children.length
? node.children.map((child) => nodeToCode(child, level + 3, repeatedItemVariable)).join('\n')
: indent(
`<pre className="min-w-0 overflow-auto rounded-lg border border-slate-200 bg-white p-3 text-xs text-slate-800 shadow-sm dark:border-slate-700 dark:bg-slate-900 dark:text-slate-100">{typeof ${repeatedItemVariable} === "string" ? ${repeatedItemVariable} : JSON.stringify(${repeatedItemVariable}, null, 2)}</pre>`,
level + 3,
)
const children = collectionExpression
? `${indent(`{Array.isArray(${collectionExpression}) ? ${collectionExpression}.map((${repeatedItemVariable}, index) => (`, level + 1)}
${indent(`<React.Fragment key={${repeatedItemVariable}?.id ?? index}>`, level + 2)}
${repeatedChildren}
${indent('</React.Fragment>', level + 2)}
${indent(')) : (', level + 1)}
${indent('<div className="rounded-lg border border-amber-300 bg-amber-50 p-3 text-xs text-amber-800 dark:border-amber-800 dark:bg-amber-950 dark:text-amber-200">Grid items bağlantısı bir koleksiyon döndürmelidir.</div>', level + 2)}
${indent(')}', level + 1)}`
: staticChildren
return `${indent(gridStart, level)}\n${children}\n${indent('</div>', level)}`
}
if (node.kind === 'platform') {
const viewNames: Record<string, string> = {
ListView: 'List',
DataGridView: 'Grid',
TreeView: 'Tree',
GanttView: 'GanttView',
TodoBoard: 'TodoBoard',
CardView: 'CardView',
SchedulerView: 'SchedulerView',
PivotView: 'Pivot',
ChartView: 'Chart',
}
return indent(
`<PlatformViewHost view=${JSON.stringify(viewNames[node.type] || node.type)} listFormCode=${JSON.stringify(String(node.props.listFormCode || ''))} height=${JSON.stringify(String(node.props.height || '420px'))} />`,
level,
)
}
if (node.type === 'Menu') {
const { items: staticOptions = [], variant: _variant, ...menuProps } = node.props
menuProps.defaultActiveKeys = normalizeDesignerKeyList(menuProps.defaultActiveKeys)
menuProps.defaultExpandedKeys = normalizeDesignerKeyList(menuProps.defaultExpandedKeys)
const { items: optionsBinding, ...menuBindings } = node.bindings || {}
const menuNode = { ...node, props: menuProps, bindings: menuBindings }
const menuPropsCode = propsToCode(menuNode, itemVariable)
const binding = optionsBinding?.sourceId
? bindingExpression(node, 'items', itemVariable)
: ''
const optionsExpression = binding
? `toSelectOptions(${binding}, ${JSON.stringify(optionsBinding?.labelPath || '')}, ${JSON.stringify(optionsBinding?.valuePath || '')})`
: JSON.stringify(Array.isArray(staticOptions) ? staticOptions : [])
return `${indent(`<UiKit.Menu${menuPropsCode}>`, level)}\n${indent(`{${optionsExpression}.map((option, optionIndex) => <UiKit.Menu.MenuItem key={String(option.value ?? optionIndex)} eventKey={String(option.value ?? optionIndex)}>{String(option.label ?? option.value ?? \`Menü \${optionIndex + 1}\`)}</UiKit.Menu.MenuItem>)}`, level + 1)}\n${indent('</UiKit.Menu>', level)}`
}
const tag =
node.type === 'checkbox'
? 'input'
: node.kind === 'ui'
? `UiKit.${node.type}`
: node.kind === 'custom'
? safeIdentifier(node.type)
: node.type
const props = { ...node.props }
if (node.type === 'checkbox') props.type = 'checkbox'
const normalizedNode = { ...node, props }
const propCode = propsToCode(normalizedNode, itemVariable)
const childrenText = String(node.props.children ?? '')
const childrenBinding = bindingExpression(node, 'children', itemVariable)
const hasChildren = node.children.length > 0 || childrenText.length > 0 || !!childrenBinding
const voidElement = ['input', 'img'].includes(tag)
if (voidElement) return indent(`<${tag}${propCode} />`, level)
if (!hasChildren) return indent(`<${tag}${propCode} />`, level)
const nested = node.children.map((child) => nodeToCode(child, level + 1, itemVariable)).join('\n')
const text = childrenBinding
? indent(`{${childrenBinding}}`, level + 1)
: childrenText
? indent(`{${JSON.stringify(childrenText)}}`, level + 1)
: ''
const body = [text, nested].filter(Boolean).join('\n')
return `${indent(`<${tag}${propCode}>`, level)}\n${body}\n${indent(`</${tag}>`, level)}`
}
export const generateDesignerCode = (name: string, document: DesignerDocument) => {
const componentName = safeIdentifier(name || 'VisualComponent')
const handlers: string[] = []
let hasSelect = false
let hasDataTable = false
const visit = (nodes: DesignerNode[]) => {
nodes.forEach((node) => {
if (node.type === 'Select' || node.type === 'AutoComplete' || node.type === 'Menu') {
hasSelect = true
}
if (node.type === 'Grid' || node.type === 'Table') hasDataTable = true
Object.entries(node.events).forEach(([eventName, script]) => {
if (!script.trim()) return
if (node.type === 'Checkbox' && eventName === 'onChange') {
handlers.push(
` const handle_${safeIdentifier(node.id)}_${eventName} = (checked, originalEvent) => {\n const event = { checked, originalEvent, target: originalEvent?.target }\n${indent(script, 2)}\n }`,
)
return
}
handlers.push(
` const handle_${safeIdentifier(node.id)}_${eventName} = (${EVENT_NAMES[eventName] || 'event'}) => {\n${indent(script, 2)}\n }`,
)
})
visit(node.children)
})
}
visit(document.nodes)
const dataSources = document.dataSources || []
const selectHelpers = hasSelect
? ` const toSelectOptions = (value, labelPath = "", valuePath = "") => {
if (!Array.isArray(value)) return []
return value.map((item, index) => {
if (item === null || typeof item !== "object" || Array.isArray(item)) return { label: String(item ?? ""), value: item ?? index }
const primitiveKeys = Object.keys(item).filter((key) => item[key] === null || ["string", "number", "boolean"].includes(typeof item[key]))
const labelKey = ["label", "name", "title", "text", "description"].find((key) => item[key] !== undefined)
const valueKey = ["value", "id", "code", "key"].find((key) => item[key] !== undefined)
const optionLabel = labelPath ? getByPath(item, labelPath) : item[labelKey ?? primitiveKeys[0]]
const optionValue = valuePath ? getByPath(item, valuePath) : item[valueKey ?? primitiveKeys[1] ?? primitiveKeys[0]]
return { ...item, label: String(optionLabel ?? \`Seçenek \${index + 1}\`), value: optionValue ?? index }
})
}`
: ''
const dataHelpers =
dataSources.length || hasDataTable
? ` const getByPath = (value, path) => {
if (!path) return value
const readPath = (target, targetPath) => targetPath.split('.').filter(Boolean).reduce((current, key) => current?.[key], target)
const directValue = readPath(value, path)
if (directValue !== undefined) return directValue
if (path.startsWith('result.')) return readPath(value, path.slice('result.'.length))
return value?.result === undefined ? undefined : readPath(value.result, path)
}`
: ''
const dataHooks = dataSources
.map((source) => {
const identifier = safeIdentifier(source.id)
if (!source.url.trim().startsWith('/api/')) {
return ` const [data_${identifier}] = React.useState(null)`
}
return ` const [data_${identifier}, setData_${identifier}] = React.useState(null)
React.useEffect(() => {
let active = true
apiService.fetchData({ url: ${JSON.stringify(source.url)}, method: 'GET' }).then((response) => {
if (active) setData_${identifier}(getByPath(response.data, ${JSON.stringify(source.responsePath || '')}))
}).catch((error) => console.error(${JSON.stringify(`${source.name} endpoint error:`)}, error))
return () => { active = false }
}, [])`
})
.join('\n\n')
const mount = document.lifecycle.onMount.trim()
? ` React.useEffect(() => {\n${indent(document.lifecycle.onMount, 2)}\n }, [])`
: ''
const body = document.nodes.map((node) => nodeToCode(node, 3)).join('\n')
const designerBackup = encodeURIComponent(JSON.stringify(document))
return `/*__SOZSOFT_VISUAL_DESIGNER__${designerBackup}__*/\nconst ${componentName} = () => {\n${[dataHelpers, selectHelpers, dataHooks, mount, ...handlers].filter(Boolean).join('\n\n')}\n\n return (\n <>\n${body}\n </>\n )\n}\n\nexport default ${componentName}\n`
}

View file

@ -0,0 +1,153 @@
import type { ComponentDefinition, PropertyInfo } from '@/proxy/developerKit/componentInfo'
export type DesignerNodeKind = 'html' | 'ui' | 'layout' | 'platform' | 'custom'
export type DesignerToolboxGroup = 'layout' | 'html' | 'platform' | 'data' | 'ui' | 'custom'
export interface DesignerBinding {
sourceId: string
path: string
labelPath?: string
valuePath?: string
}
export interface DesignerDataSource {
id: string
name: string
method: 'GET'
url: string
responsePath: string
}
export interface DesignerNode {
id: string
type: string
kind: DesignerNodeKind
props: Record<string, unknown>
events: Record<string, string>
bindings: Record<string, DesignerBinding>
children: DesignerNode[]
}
export interface DesignerDocument {
version: 1
sourceMode: 'visual' | 'code'
nodes: DesignerNode[]
canvas: {
background: string
width: 'responsive' | 'desktop' | 'tablet' | 'mobile'
}
lifecycle: {
onMount: string
}
dataSources: DesignerDataSource[]
}
export interface DesignerPropertyInfo extends PropertyInfo {
tsType?: string
required?: boolean
}
export interface DesignerComponentDefinition extends Omit<ComponentDefinition, 'properties'> {
kind: DesignerNodeKind
toolboxGroup: DesignerToolboxGroup
properties: DesignerPropertyInfo[]
acceptsChildren?: boolean
sourceName?: string
sourceFile?: string
interfaceName?: string
}
export const createEmptyDesignerDocument = (
sourceMode: 'visual' | 'code' = 'visual',
): DesignerDocument => ({
version: 1,
sourceMode,
nodes: [],
canvas: {
background: '#f8fafc',
width: 'responsive',
},
lifecycle: {
onMount: '',
},
dataSources: [],
})
export const createDesignerId = () =>
`cmp_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 7)}`
export const walkDesignerNodes = (
nodes: DesignerNode[],
callback: (node: DesignerNode, parentId: string | null) => void,
parentId: string | null = null,
) => {
nodes.forEach((node) => {
callback(node, parentId)
walkDesignerNodes(node.children, callback, node.id)
})
}
export const findDesignerNode = (nodes: DesignerNode[], id: string | null): DesignerNode | null => {
if (!id) return null
for (const node of nodes) {
if (node.id === id) return node
const child = findDesignerNode(node.children, id)
if (child) return child
}
return null
}
export const getDesignerValueByPath = (value: unknown, path: string): unknown => {
if (!path.trim()) return value
return path
.split('.')
.filter(Boolean)
.reduce<unknown>((current, key) => {
if (current === null || current === undefined || typeof current !== 'object') return undefined
return (current as Record<string, unknown>)[key]
}, value)
}
export const resolveDesignerResponse = (value: unknown, responsePath: string): unknown => {
if (!responsePath.trim()) return value
const directValue = getDesignerValueByPath(value, responsePath)
if (directValue !== undefined) return directValue
// Supports both ABP's older `result` envelope and current direct DTO responses.
if (responsePath.startsWith('result.')) {
return getDesignerValueByPath(value, responsePath.slice('result.'.length))
}
if (value && typeof value === 'object' && 'result' in value) {
return getDesignerValueByPath((value as Record<string, unknown>).result, responsePath)
}
return undefined
}
export const normalizeDesignerKeyList = (value: unknown): string[] => {
let candidate = value
if (typeof candidate === 'string') {
const rawValue = candidate.trim()
const jsonValue = rawValue.startsWith('{') && rawValue.endsWith('}')
? rawValue.slice(1, -1).trim()
: rawValue
try {
candidate = JSON.parse(jsonValue)
} catch {
candidate = rawValue ? [rawValue] : []
}
}
if (!Array.isArray(candidate)) return []
return candidate
.filter((item): item is string | number =>
typeof item === 'string' || typeof item === 'number',
)
.map(String)
.filter(Boolean)
}

View file

@ -4,8 +4,20 @@ import type {
CustomComponentDto,
} from '@/proxy/developerKit/models'
import { developerKitService } from '@/services/developerKit.service'
import apiService from '@/services/api.service'
import { generateDesignerCode } from '@/components/visualDesigner/codeGenerator'
import type { DesignerDocument } from '@/components/visualDesigner/types'
import DOMPurify from 'dompurify'
import { useStoreState } from '@/store/store'
import React, { createContext, useContext, useState, useEffect, useCallback, useMemo } from 'react'
import React, {
createContext,
useContext,
useState,
useEffect,
useCallback,
useMemo,
useRef,
} from 'react'
interface ComponentProps {
[key: string]: unknown
@ -33,6 +45,65 @@ interface ComponentContextType {
}
const ComponentContext = createContext<ComponentContextType | undefined>(undefined)
const COMPONENTS_CHANGED_STORAGE_KEY = 'sozsoft:custom-components:changed'
const notifyComponentsChanged = () => {
try {
window.localStorage.setItem(COMPONENTS_CHANGED_STORAGE_KEY, String(Date.now()))
} catch {
// Local state is already current; storage can be unavailable in restricted browsers.
}
}
const haveSameComponentRevision = (
current: CustomComponent[],
next: CustomComponent[],
) =>
current.length === next.length &&
current.every((component, index) => {
const candidate = next[index]
return (
component.id === candidate?.id &&
component.name === candidate.name &&
component.routePath === candidate.routePath &&
component.code === candidate.code &&
component.props === candidate.props &&
component.description === candidate.description &&
component.dependencies === candidate.dependencies &&
component.isActive === candidate.isActive &&
component.lastModificationTime === candidate.lastModificationTime
)
})
const normalizeComponentCodeForBundle = (code: string) =>
code
// Stored components run inside a shared function scope. Imports are supplied
// by that scope and therefore cannot remain as nested ESM declarations.
.replace(/^\s*import\s+(?:(?:type\s+)?[\s\S]*?\s+from\s+)?['"][^'"\r\n]+['"]\s*;?\s*$/gm, '')
.replace(/\bexport\s+default\s+/g, '')
.trim()
const toJavaScriptIdentifier = (value: string) => {
const identifier = value.replace(/[^A-Za-z0-9_$]/g, '_')
return /^[A-Za-z_$]/.test(identifier) ? identifier : `Component_${identifier}`
}
const getComponentRuntimeCode = (component: CustomComponent) => {
try {
const props = component.props ? JSON.parse(component.props) : null
const document = props?.visualDesigner as DesignerDocument | undefined
if (
document?.version === 1 &&
document.sourceMode === 'visual' &&
Array.isArray(document.nodes)
) {
return generateDesignerCode(component.name, document)
}
} catch (error) {
console.warn(`Visual designer schema could not be read for ${component.name}:`, error)
}
return component.code
}
const HelloWorldComponent: React.ComponentType<unknown> = () =>
React.createElement('div', { className: 'p-6 bg-blue-50 rounded-lg' }, [
@ -85,22 +156,50 @@ export const ComponentProvider: React.FC<{ children: React.ReactNode }> = ({ chi
const [compiledComponents, setCompiledComponents] = useState<
Record<string, React.ComponentType<ComponentProps>>
>({})
const refreshInFlightRef = useRef<Promise<void> | null>(null)
const refreshComponents = useCallback(async () => {
try {
setError(null)
const refreshComponents = useCallback(() => {
if (refreshInFlightRef.current) return refreshInFlightRef.current
const customComponents = extraProperties?.customComponents as CustomComponentDto[]
setComponents(customComponents || [])
} catch (err) {
setError(err instanceof Error ? err.message : 'Failed to fetch components')
console.error('Failed to fetch components:', err)
setComponents([])
}
const request = (async () => {
try {
setError(null)
const result = await developerKitService.getCustomComponents()
const nextComponents = result.items || []
setComponents((current) =>
haveSameComponentRevision(current, nextComponents) ? current : nextComponents,
)
} catch (err) {
setError(err instanceof Error ? err.message : 'Failed to fetch components')
console.error('Failed to fetch components:', err)
const fallbackComponents = extraProperties?.customComponents as CustomComponentDto[]
setComponents((current) => (current.length ? current : fallbackComponents || []))
}
})()
refreshInFlightRef.current = request
void request.finally(() => {
if (refreshInFlightRef.current === request) refreshInFlightRef.current = null
})
return request
}, [extraProperties])
useEffect(() => {
refreshComponents()
void refreshComponents()
}, [refreshComponents])
useEffect(() => {
const handleFocus = () => void refreshComponents()
const handleStorage = (event: StorageEvent) => {
if (event.key === COMPONENTS_CHANGED_STORAGE_KEY) void refreshComponents()
}
window.addEventListener('focus', handleFocus)
window.addEventListener('storage', handleStorage)
return () => {
window.removeEventListener('focus', handleFocus)
window.removeEventListener('storage', handleStorage)
}
}, [refreshComponents])
const addComponent = useCallback(async (componentData: CreateUpdateCustomComponentDto) => {
@ -109,6 +208,7 @@ export const ComponentProvider: React.FC<{ children: React.ReactNode }> = ({ chi
setError(null)
const newComponent = await developerKitService.createCustomComponent(componentData)
setComponents((prev) => [...prev, newComponent])
notifyComponentsChanged()
} catch (err) {
setError(err instanceof Error ? err.message : 'Failed to create component')
throw err
@ -117,24 +217,25 @@ export const ComponentProvider: React.FC<{ children: React.ReactNode }> = ({ chi
}
}, [])
const updateComponent = useCallback(async (
id: string,
componentData: CreateUpdateCustomComponentDto,
) => {
try {
setLoading(true)
setError(null)
const updatedComponent = await developerKitService.updateCustomComponent(id, componentData)
setComponents((prev) =>
prev.map((component) => (component.id === id ? updatedComponent : component)),
)
} catch (err) {
setError(err instanceof Error ? err.message : 'Failed to update component')
throw err
} finally {
setLoading(false)
}
}, [])
const updateComponent = useCallback(
async (id: string, componentData: CreateUpdateCustomComponentDto) => {
try {
setLoading(true)
setError(null)
const updatedComponent = await developerKitService.updateCustomComponent(id, componentData)
setComponents((prev) =>
prev.map((component) => (component.id === id ? updatedComponent : component)),
)
notifyComponentsChanged()
} catch (err) {
setError(err instanceof Error ? err.message : 'Failed to update component')
throw err
} finally {
setLoading(false)
}
},
[],
)
const deleteComponent = useCallback(async (id: string) => {
try {
@ -142,6 +243,7 @@ export const ComponentProvider: React.FC<{ children: React.ReactNode }> = ({ chi
setError(null)
await developerKitService.deleteCustomComponent(id)
setComponents((prev) => prev.filter((component) => component.id !== id))
notifyComponentsChanged()
} catch (err) {
setError(err instanceof Error ? err.message : 'Failed to delete component')
throw err
@ -159,7 +261,12 @@ export const ComponentProvider: React.FC<{ children: React.ReactNode }> = ({ chi
[components],
)
const activeComponentsByName = useMemo(
() => new Map(components.filter((component) => component.isActive).map((component) => [component.name, component])),
() =>
new Map(
components
.filter((component) => component.isActive)
.map((component) => [component.name, component]),
),
[components],
)
@ -170,14 +277,11 @@ export const ComponentProvider: React.FC<{ children: React.ReactNode }> = ({ chi
[componentsByName],
)
const registerComponent = useCallback(
(name: string, component: React.ComponentType<unknown>) => {
setRegisteredComponents((previous) =>
previous[name] === component ? previous : { ...previous, [name]: component },
)
},
[],
)
const registerComponent = useCallback((name: string, component: React.ComponentType<unknown>) => {
setRegisteredComponents((previous) =>
previous[name] === component ? previous : { ...previous, [name]: component },
)
}, [])
// Component compilation functions (moved from ComponentRegistryProvider)
const extractComponentInfo = useCallback((code: string, defaultName = '') => {
@ -223,35 +327,50 @@ export const ComponentProvider: React.FC<{ children: React.ReactNode }> = ({ chi
return
}
const componentInfos = activeComponents.map((comp) => {
const componentInfos = activeComponents.map((comp, index) => {
const name = comp.name
const nameCapitalized = name.charAt(0).toUpperCase() + name.slice(1)
const runtimeCode = getComponentRuntimeCode(comp)
const referenceName = toJavaScriptIdentifier(nameCapitalized)
return {
name: name,
nameCapitalized: nameCapitalized,
internalName: extractComponentInfo(comp.code, nameCapitalized),
code: comp.code
.replace(/import\s+.*?;/g, '')
.replace(/export\s+default\s+/, '')
.trim(),
referenceName,
bundleVariable: `__component_${index}`,
internalName: extractComponentInfo(runtimeCode, referenceName),
code: normalizeComponentCodeForBundle(runtimeCode),
}
})
// Create cross-referencing bundle
const componentDeclarations = componentInfos
.map((info) => `let ${info.name}_Component;`)
.map((info) => `let ${info.bundleVariable};`)
.join('\n')
const componentDefinitions = componentInfos
.map((info) => {
const declaredReferences = new Set<string>()
const componentVariables = componentInfos
.filter((other) => other.name !== info.name)
.map((other) => `const ${other.name} = ${other.name}_Component;`)
.filter((other) => {
if (
other.referenceName === info.internalName ||
declaredReferences.has(other.referenceName)
) {
return false
}
declaredReferences.add(other.referenceName)
return true
})
.map(
(other) =>
`const ${other.referenceName} = (componentProps) => React.createElement(${other.bundleVariable}, componentProps);`,
)
.join('\n ')
return `
${info.name}_Component = (function() {
${info.bundleVariable} = (function() {
${componentVariables}
${info.code}
return ${info.internalName};
@ -262,7 +381,7 @@ export const ComponentProvider: React.FC<{ children: React.ReactNode }> = ({ chi
const componentBundle = componentDeclarations + '\n' + componentDefinitions
const bundledCode = `
(function(React, Alert, Avatar, Badge, Button, Calendar, Card, Checkbox, ConfigProvider, DatePicker, Dialog, Drawer, Dropdown, FormItem, FormContainer, Input, InputGroup, Menu, MenuItem, Notification, Pagination, Progress, Radio, RangeCalendar, ScrollBar, Segment, Select, Skeleton, Spinner, Steps, Switcher, Table, Tabs, Tag, TimeInput, Timeline, toast, Tooltip, Upload, axios) {
(function(React, AutoComplete, Alert, Avatar, Badge, Button, Calendar, Card, Checkbox, ConfigProvider, DatePicker, Dialog, Drawer, Dropdown, FormItem, FormContainer, Input, InputGroup, Menu, MenuItem, Notification, Pagination, Progress, Radio, RangeCalendar, ScrollBar, Segment, Select, Skeleton, Spinner, Steps, Switcher, Table, Tabs, Tag, TimeInput, Timeline, toast, Tooltip, Upload, PlatformViewHost, apiService, DOMPurify, axios, UiKit) {
const { useState, useEffect, useCallback, useMemo, useRef, createContext, useContext } = React;
const componentRegistry = {};
@ -271,26 +390,28 @@ export const ComponentProvider: React.FC<{ children: React.ReactNode }> = ({ chi
${componentInfos
.map(
(info) => `
componentRegistry["${info.name}"] = ${info.name}_Component;
componentRegistry["${info.nameCapitalized}"] = ${info.name}_Component;
componentRegistry[${JSON.stringify(info.name)}] = ${info.bundleVariable};
componentRegistry[${JSON.stringify(info.nameCapitalized)}] = ${info.bundleVariable};
`,
)
.join('\n')}
return componentRegistry;
})(React, Alert, Avatar, Badge, Button, Calendar, Card, Checkbox, ConfigProvider, DatePicker, Dialog, Drawer, Dropdown, FormItem, FormContainer, Input, InputGroup, Menu, MenuItem, Notification, Pagination, Progress, Radio, RangeCalendar, ScrollBar, Segment, Select, Skeleton, Spinner, Steps, Switcher, Table, Tabs, Tag, TimeInput, Timeline, toast, Tooltip, Upload, axios)
})(React, AutoComplete, Alert, Avatar, Badge, Button, Calendar, Card, Checkbox, ConfigProvider, DatePicker, Dialog, Drawer, Dropdown, FormItem, FormContainer, Input, InputGroup, Menu, MenuItem, Notification, Pagination, Progress, Radio, RangeCalendar, ScrollBar, Segment, Select, Skeleton, Spinner, Steps, Switcher, Table, Tabs, Tag, TimeInput, Timeline, toast, Tooltip, Upload, PlatformViewHost, apiService, DOMPurify, axios, UiKit)
`
// Babel is several megabytes and is only needed when an active runtime
// component exists. Keep it out of the application startup bundle.
const [Babel, ui, { default: axios }] = await Promise.all([
const [Babel, ui, { default: axios }, { default: PlatformViewHost }] = await Promise.all([
import('@babel/standalone'),
import('../components/ui'),
import('axios'),
import('../components/componentEditor/PlatformViewHost'),
])
if (cancelled) return
const {
AutoComplete,
Alert,
Avatar,
Badge,
@ -342,6 +463,7 @@ export const ComponentProvider: React.FC<{ children: React.ReactNode }> = ({ chi
const componentsFactory = new Function(
'React',
'AutoComplete',
'Alert',
'Avatar',
'Badge',
@ -380,12 +502,17 @@ export const ComponentProvider: React.FC<{ children: React.ReactNode }> = ({ chi
'toast',
'Tooltip',
'Upload',
'PlatformViewHost',
'apiService',
'DOMPurify',
'axios',
'UiKit',
`return ${compiledBundle}`,
)
const compiledComponentsRegistry = componentsFactory(
React,
AutoComplete,
Alert,
Avatar,
Badge,
@ -424,7 +551,11 @@ export const ComponentProvider: React.FC<{ children: React.ReactNode }> = ({ chi
toast,
Tooltip,
Upload,
PlatformViewHost,
apiService,
DOMPurify,
axios,
ui,
)
if (!cancelled) setCompiledComponents(compiledComponentsRegistry)

View file

@ -1,15 +1,3 @@
export interface ComponentInfo {
id: string;
name: string;
type: string;
props: Record<string, any>;
children?: ComponentInfo[] | string;
startLine: number;
endLine: number;
startColumn: number;
endColumn: number;
}
export interface PropertyInfo {
name: string;
type:
@ -25,8 +13,7 @@ export interface PropertyInfo {
description?: string;
category: "properties" | "events" | "styling";
}
export interface HookInfo {
interface HookInfo {
name: string;
type:
| "useState"
@ -49,14 +36,3 @@ export interface ComponentDefinition {
hooks: HookInfo[];
defaultProps?: Record<string, any>;
}
export interface TailwindClass {
category: string;
classes: string[];
}
export interface EditorState {
code: string;
components: ComponentInfo[];
selectedComponentId: string | null;
}

View file

@ -14,6 +14,7 @@ class DeveloperKitService {
const response = await apiService.fetchData<PagedResultDto<CustomComponentDto>>({
url: '/api/app/custom-component',
method: 'GET',
params: { maxResultCount: 1000 },
})
return response.data
}
@ -47,10 +48,14 @@ class DeveloperKitService {
id: string,
component: CreateUpdateCustomComponentDto,
): Promise<CustomComponent> {
const payload =
component.props === undefined
? { ...component, props: (await this.getCustomComponent(id)).props }
: component
const response = await apiService.fetchData<CustomComponent>({
url: `/api/app/custom-component/${id}`,
method: 'PUT',
data: component as any,
data: payload as any,
})
return response.data
}
@ -91,6 +96,7 @@ class DeveloperKitService {
const response = await apiService.fetchData<PagedResultDto<CrudEndpoint>>({
url: '/api/app/crud-endpoint-generate',
method: 'GET',
params: { maxResultCount: 1000 },
})
return response.data
}

View file

@ -1,799 +0,0 @@
import * as parser from "@babel/parser";
import traverse from "@babel/traverse";
import * as t from "@babel/types";
import generate from "@babel/generator";
import { ComponentInfo } from "../proxy/developerKit/componentInfo";
export interface ParsedComponent {
components: ComponentInfo[];
imports: string[];
hooks: string[];
}
export const generateUniqueId = (): string => {
// Include timestamp for better uniqueness
return (
"c_" +
Date.now().toString(36) +
"_" +
Math.random().toString(36).substring(2, 8)
);
};
export const generateSingleComponentJSX = (
type: string,
props: Record<string, { type: string; value: any }>
): string => {
const attributes = Object.entries(props)
.filter(([key]) => key !== "children")
.map(([key, propInfo]) => {
const { type, value } = propInfo;
// null ve boş değerleri ekleme
if (value === null || value === "") return "";
// object tipindeki boş nesneleri atla
if (
type === "object" &&
(value === "" ||
(typeof value === "object" &&
value !== null &&
Object.keys(value).length === 0))
) {
return "";
}
// false boolean'ları atla
if (type === "boolean" && value === false) return "";
// true boolean'ları yalnızca anahtar olarak ekle
if (type === "boolean" && value === true) return `${key}`;
// number her zaman eklenir
if (type === "number") return `${key}={${value}}`;
// Diğer her şey
return `${key}=${JSON.stringify(value)}`;
})
.filter(Boolean)
.join(" ");
const children = props.children?.value ?? "";
return `<${type}${attributes ? " " + attributes : ""}>${children}</${type}>`;
};
export const parseReactCode = (code: string): ParsedComponent => {
try {
// Clean up duplicate imports before parsing
const cleanCode = cleanupDuplicateImports(code);
const ast = parser.parse(cleanCode, {
sourceType: "module",
plugins: ["jsx", "typescript"],
});
const components: ComponentInfo[] = [];
const imports: string[] = [];
const hooks: string[] = [];
traverse(ast, {
ImportDeclaration(path) {
if (path.node.source.value === "react") {
path.node.specifiers.forEach((spec) => {
if (t.isImportSpecifier(spec) && t.isIdentifier(spec.imported)) {
imports.push(spec.imported.name);
}
});
}
},
CallExpression(path) {
if (t.isIdentifier(path.node.callee)) {
const functionName = path.node.callee.name;
if (functionName.startsWith("use")) {
hooks.push(functionName);
}
}
},
JSXElement(path) {
const element = path.node;
if (t.isJSXIdentifier(element.openingElement.name)) {
const componentName = element.openingElement.name.name;
const props: Record<string, any> = {};
// Extract props
element.openingElement.attributes.forEach((attr) => {
if (t.isJSXAttribute(attr) && t.isJSXIdentifier(attr.name)) {
const propName = attr.name.name;
let propValue: any = "";
if (attr.value) {
if (t.isStringLiteral(attr.value)) {
propValue = attr.value.value;
} else if (t.isJSXExpressionContainer(attr.value)) {
if (t.isStringLiteral(attr.value.expression)) {
propValue = attr.value.expression.value;
} else if (t.isBooleanLiteral(attr.value.expression)) {
propValue = attr.value.expression.value;
} else if (t.isNumericLiteral(attr.value.expression)) {
propValue = attr.value.expression.value;
} else {
// For complex expressions, store as string
propValue = cleanCode.slice(
attr.value.expression.start!,
attr.value.expression.end!
);
}
}
} else {
propValue = true; // Boolean prop without value
}
props[propName] = propValue;
}
});
// Add unique ID if not present
if (!props.id || !props.id.startsWith("c_")) {
props.id = generateUniqueId();
}
// Extract children content - only if it's simple text/expression content
const childrenContent = extractChildrenContent(element, cleanCode);
const hasNestedElements =
element.children &&
element.children.some(
(child) => t.isJSXElement(child) || t.isJSXFragment(child)
);
const component: ComponentInfo = {
id: props.id,
name: componentName,
type: componentName.toLowerCase(),
props,
children: hasNestedElements ? undefined : childrenContent,
startLine: element.loc?.start.line || 0,
endLine: element.loc?.end.line || 0,
startColumn: element.loc?.start.column || 0,
endColumn: element.loc?.end.column || 0,
};
components.push(component);
}
},
});
return { components, imports, hooks };
} catch (error) {
console.error("Error parsing React code:", error);
return { components: [], imports: [], hooks: [] };
}
};
const extractChildrenContent = (element: any, code: string): string => {
if (!element.children || element.children.length === 0) {
return "";
}
// Get the content between opening and closing tags
const start = element.openingElement.end;
const end = element.closingElement
? element.closingElement.start
: element.end;
if (start && end && start < end) {
return code.slice(start, end).trim();
}
return "";
};
export const generateHookVariableName = (
hookType: string,
componentId: string
): string => {
// Use the full componentId without shortening
const cleanId = componentId.replace("c_", ""); // Remove c_ prefix but keep the rest
switch (hookType) {
case "useState":
return `state_c_${cleanId}`;
case "useRef":
return `ref_c_${cleanId}`;
case "useEffect":
return `effect_c_${cleanId}`;
case "useCallback":
return `callback_c_${cleanId}`;
case "useMemo":
return `memo_c_${cleanId}`;
default:
return `hook_c_${cleanId}`;
}
};
export const removeHookFromCode = (
code: string,
hookType: string,
componentId: string
): string => {
const varName = generateHookVariableName(hookType, componentId);
// Sadece hook tanım satırını sil
// ^\s*const\s+\[?.*?varName.*?\]?\s*=\s*hookType\(.*?\);\s*$
// Bu satırı, başında ve sonunda yalnızca bir satırı siler şekilde tasarla
const hookLineRegex = new RegExp(
`^\\s*const\\s+(?:\\[.*?${varName}.*?\\]|${varName})\\s*=\\s*${hookType}\\([^)]*\\);?\\s*$`,
"gm"
);
code = code.replace(hookLineRegex, "");
// Import'tan kaldırma mantığı aynı kalabilir
const remainingHooks = code.match(new RegExp(`${hookType}\\(`, "g"));
if (!remainingHooks || remainingHooks.length === 0) {
const reactImportRegex =
// eslint-disable-next-line no-useless-escape
/import\s+(React\s*,\s*)?\{([^}]*)\}\s+from\s+['"]react['\"];?\s*/g;
let match;
let newCode = code;
while ((match = reactImportRegex.exec(code)) !== null) {
const importLine = match[0];
const hooks = match[2]
.split(",")
.map((h) => h.trim())
.filter((h) => h && h !== hookType);
let newImport = "";
if (hooks.length > 0) {
newImport = match[1]
? `import React, { ${hooks.join(", ")} } from 'react';\n`
: `import { ${hooks.join(", ")} } from 'react';\n`;
} else {
newImport = match[1] ? `import React from 'react';\n` : "";
}
newCode = newCode.replace(importLine, newImport);
}
code = newCode;
}
// Duplicate import cleanup aynı kalabilir
code = cleanupDuplicateImports(code);
return code;
};
export const insertJSXAtPosition = (
code: string,
jsx: string,
position: { line: number; column: number }
): string => {
const lines = code.split("\n");
// JSX kodunu istenen satıra ekle
lines.splice(position.line, 0, jsx);
return lines.join("\n");
};
export const generateComponentJSX = (component: ComponentInfo): string => {
const { type, props } = component;
const propsString = Object.entries(props)
.filter(([key, val]) => {
if (
key === "children" ||
!val ||
typeof val !== "object" ||
!("value" in val)
)
return false;
const isFunction = val.type === "function";
const isObject = val.type === "object";
const isBoolean = typeof val.value === "boolean";
return (
val.value !== null &&
val.value !== undefined &&
(isFunction || isBoolean || val.value !== "") &&
(isObject || Object.keys(val.value).length > 0)
);
})
.map(([key, val]) => {
const propVal =
val && typeof val === "object" && "value" in val ? val.value : val;
if (typeof propVal === "string") return `${key}="${propVal}"`;
if (typeof propVal === "number") return `${key}={${propVal}}`;
if (typeof propVal === "boolean")
return propVal ? `${key}` : `${key}={false}`;
return `${key}={${JSON.stringify(propVal)}}`;
})
.join(" ");
console.log("📝 App: Generated props string:", propsString);
const children =
props.children &&
typeof props.children === "object" &&
"value" in props.children
? props.children.value
: "";
const hasProps = propsString.length > 0;
const hasChildren = children && children.length > 0;
if (hasChildren) {
return `<${type} id="${component.id}"${
hasProps ? " " + propsString : ""
}>${children}</${type}>`;
} else {
return `<${type} id="${component.id}"${
hasProps ? " " + propsString : ""
} />`;
}
};
export const generateHookCode = (
hookType: string,
componentId: string,
componentType: string,
initialValue?: any
): string => {
const varName = generateHookVariableName(hookType, componentId);
switch (hookType) {
case "useState":
const defaultValue = getDefaultValueForComponent(
componentType,
initialValue
);
// Generate proper camelCase setter name from state variable
const setterName = `set${
varName.charAt(0).toUpperCase() + varName.slice(1)
}`;
return `const [${varName}, ${setterName}] = useState(${defaultValue});`;
case "useRef":
return `const ${varName} = useRef(null);`;
case "useCallback":
return `const ${varName} = useCallback(() => {\n // Callback logic here\n }, []);`;
case "useMemo":
return `const ${varName} = useMemo(() => {\n // Memo logic here\n }, []);`;
default:
return `const ${varName} = ${hookType}();`;
}
};
const getDefaultValueForComponent = (
componentType: string,
initialValue?: any
): string => {
if (initialValue !== undefined) {
return typeof initialValue === "string"
? `'${initialValue}'`
: String(initialValue);
}
switch (componentType) {
case "input":
case "textarea":
return "''";
case "checkbox":
return "false";
case "select":
return "''";
case "button":
return "false";
default:
return "''";
}
};
export const updateComponentProp = (
code: string,
componentId: string,
propName: string,
propValue: any
): string => {
// Special handling for children
if (propName === "children") {
return updateComponentChildren(code, componentId, propValue);
}
console.log("🔧 updateComponentProp called:", {
componentId,
propName,
propValue,
propType: typeof propValue,
});
// Clean up duplicate imports before parsing
const cleanCode = cleanupDuplicateImports(code);
try {
const ast = parser.parse(cleanCode, {
sourceType: "module",
plugins: ["jsx", "typescript"],
});
let updatedCode = cleanCode;
let offset = 0;
traverse(ast, {
JSXElement(path) {
const element = path.node;
if (t.isJSXIdentifier(element.openingElement.name)) {
// Find the component with matching ID
const idAttr = element.openingElement.attributes.find(
(attr) =>
t.isJSXAttribute(attr) &&
t.isJSXIdentifier(attr.name) &&
attr.name.name === "id" &&
attr.value &&
t.isStringLiteral(attr.value) &&
attr.value.value === componentId
);
if (idAttr) {
// Find existing prop or add new one
const existingPropIndex =
element.openingElement.attributes.findIndex(
(attr) =>
t.isJSXAttribute(attr) &&
t.isJSXIdentifier(attr.name) &&
attr.name.name === propName
);
console.log("📍 Existing prop index:", existingPropIndex);
// Generate proper JSX attribute string
let newPropString: string | null = null;
let shouldRemoveAttribute = false;
if (
propValue === null ||
propValue === undefined ||
propValue === ""
) {
shouldRemoveAttribute = true;
console.log("🗑️ Will remove attribute - value is:", propValue);
} else if (propValue === true) {
newPropString = propName; // Boolean true: just the attribute name
console.log("✅ Boolean true prop:", newPropString);
} else if (typeof propValue === "string") {
// Handle event props and JSX expressions
if (propName.startsWith("on") || propValue.startsWith("{")) {
// Event props or JSX expressions - ensure proper braces
if (propValue.startsWith("{") && propValue.endsWith("}")) {
newPropString = `${propName}=${propValue}`;
} else {
newPropString = `${propName}={${propValue}}`;
}
console.log("🎯 Event/JSX prop:", newPropString);
} else {
newPropString = `${propName}="${propValue}"`; // String literal
console.log("📝 String prop:", newPropString);
}
} else if (typeof propValue === "boolean") {
newPropString = `${propName}={${propValue}}`;
console.log("🔘 Boolean prop:", newPropString);
} else {
newPropString = `${propName}={${propValue}}`; // Other values as JSX expression
console.log("🔢 Other prop:", newPropString);
}
if (existingPropIndex !== -1) {
// Update existing prop
const existingProp =
element.openingElement.attributes[existingPropIndex];
if (t.isJSXAttribute(existingProp)) {
const attrStart = existingProp.start! + offset;
const attrEnd = existingProp.end! + offset;
console.log(
"🔄 Updating existing prop at position:",
attrStart,
"-",
attrEnd
);
console.log(
"🔄 Old attribute:",
updatedCode.slice(attrStart, attrEnd)
);
if (shouldRemoveAttribute) {
// Remove the entire attribute with proper whitespace handling
let removeStart = attrStart;
const removeEnd = attrEnd;
// Look for whitespace/newline before the attribute
while (removeStart > 0) {
const char = updatedCode[removeStart - 1];
if (
char === " " ||
char === "\t" ||
char === "\n" ||
char === "\r"
) {
removeStart--;
} else {
break;
}
}
// Make sure we don't remove too much - keep at least one space if needed
const beforeChar =
removeStart > 0 ? updatedCode[removeStart - 1] : "";
const afterChar =
removeEnd < updatedCode.length
? updatedCode[removeEnd]
: "";
// If we're between two attributes or after tag name, ensure proper spacing
if (
beforeChar &&
beforeChar !== " " &&
beforeChar !== "\n" &&
beforeChar !== "\t" &&
afterChar &&
afterChar !== " " &&
afterChar !== "\n" &&
afterChar !== "\t" &&
afterChar !== ">" &&
afterChar !== "/"
) {
// Insert a space to prevent attributes from merging
updatedCode =
updatedCode.slice(0, removeStart) +
" " +
updatedCode.slice(removeEnd);
offset -= removeEnd - removeStart - 1; // -1 because we added a space
} else {
updatedCode =
updatedCode.slice(0, removeStart) +
updatedCode.slice(removeEnd);
offset -= removeEnd - removeStart;
}
console.log(
"🗑️ Removed attribute from",
removeStart,
"to",
removeEnd
);
} else {
// Replace the entire attribute
if (newPropString) {
console.log("🔄 Replacing with:", newPropString);
updatedCode =
updatedCode.slice(0, attrStart) +
newPropString +
updatedCode.slice(attrEnd);
offset += newPropString.length - (attrEnd - attrStart);
}
}
}
// Stop traversal after modifying the target component
path.stop();
} else if (!shouldRemoveAttribute && newPropString) {
// Add new prop
const insertPos = element.openingElement.name.end! + offset;
const propToInsert = ` ${newPropString}`;
console.log(
" Adding new prop at position",
insertPos,
":",
propToInsert
);
updatedCode =
updatedCode.slice(0, insertPos) +
propToInsert +
updatedCode.slice(insertPos);
offset += propToInsert.length;
}
// Stop traversal after processing the target component
path.stop();
}
}
},
});
console.log(
"✅ updateComponentProp completed. Code changed:",
code !== updatedCode
);
return updatedCode;
} catch (error) {
console.error("Error updating component prop:", error);
return code;
}
};
const cleanupDuplicateImports = (code: string): string => {
// Remove duplicate React imports
const reactImportRegex =
/import\s+React(?:\s*,\s*\{[^}]*\})?\s+from\s+['"]react['"];\s*/g;
const reactImports = code.match(reactImportRegex);
if (reactImports && reactImports.length > 1) {
// Collect all hooks from all imports
const allHooks = new Set<string>();
reactImports.forEach((importLine) => {
const hooksMatch = importLine.match(/\{([^}]*)\}/);
if (hooksMatch) {
const hooks = hooksMatch[1]
.split(",")
.map((h) => h.trim())
.filter((h) => h);
hooks.forEach((hook) => allHooks.add(hook));
}
});
// Remove all React imports
let cleanedCode = code.replace(reactImportRegex, "");
// Add single consolidated import
if (allHooks.size > 0) {
const consolidatedImport = `import React, { ${Array.from(allHooks).join(
", "
)} } from 'react';\n`;
cleanedCode = consolidatedImport + cleanedCode;
} // else: Hiç import ekleme
console.log("✅ Consolidated imports:", Array.from(allHooks));
return cleanedCode;
}
return code;
};
export const updateComponentProps = (
code: string,
componentId: string,
updates: Record<string, any>
): string => {
console.log("🔄 updateComponentProps called:", { componentId, updates });
// Apply each update individually to avoid offset calculation issues
let updatedCode = code;
Object.entries(updates).forEach(([propName, propValue]) => {
console.log(`<EFBFBD> Processing update: ${propName} = ${propValue}`);
updatedCode = updateComponentProp(
updatedCode,
componentId,
propName,
propValue
);
});
console.log(
"✅ updateComponentProps completed. Code changed:",
code !== updatedCode
);
return updatedCode;
};
const updateComponentChildren = (
code: string,
componentId: string,
newChildren: string
): string => {
try {
const ast = parser.parse(code, {
sourceType: "module",
plugins: ["jsx", "typescript"],
});
let updatedCode = code;
let offset = 0;
traverse(ast, {
JSXElement(path) {
const element = path.node;
if (t.isJSXIdentifier(element.openingElement.name)) {
// Find the component with matching ID
const idAttr = element.openingElement.attributes.find(
(attr) =>
t.isJSXAttribute(attr) &&
t.isJSXIdentifier(attr.name) &&
attr.name.name === "id" &&
attr.value &&
t.isStringLiteral(attr.value) &&
attr.value.value === componentId
);
if (idAttr && element.closingElement) {
// Update children content
const start = element.openingElement.end! + offset;
const end = element.closingElement.start! + offset;
updatedCode =
updatedCode.slice(0, start) +
newChildren +
updatedCode.slice(end);
offset += newChildren.length - (end - start);
// Stop traversal after modifying the target component
path.stop();
}
}
},
});
return updatedCode;
} catch (error) {
console.error("Error updating component children:", error);
return code;
}
};
export function removeComponentAndHooksFromCode(
code: string,
componentId: string
): string {
let ast;
try {
ast = parser.parse(code, {
sourceType: "module",
plugins: ["jsx", "typescript"],
});
} catch (e) {
console.error("Parse error:", e);
return code;
}
// 1. JSX'i sil
traverse(ast, {
JSXElement(path) {
const el = path.node;
if (t.isJSXIdentifier(el.openingElement.name)) {
const idAttr = el.openingElement.attributes.find(
(attr) =>
t.isJSXAttribute(attr) &&
t.isJSXIdentifier(attr.name) &&
attr.name.name === "id" &&
attr.value &&
t.isStringLiteral(attr.value) &&
attr.value.value === componentId
);
if (idAttr) {
path.remove();
}
}
},
});
// 2. useState ve useRef hooklarını sil
traverse(ast, {
VariableDeclaration(path) {
const decl = path.node.declarations[0];
// useState
if (
t.isVariableDeclarator(decl) &&
t.isArrayPattern(decl.id) &&
t.isCallExpression(decl.init) &&
t.isIdentifier(decl.init.callee, { name: "useState" }) &&
decl.id.elements[0] &&
t.isIdentifier(decl.id.elements[0]) &&
decl.id.elements[0].name.includes(componentId)
) {
path.remove();
}
// useRef
if (
t.isVariableDeclarator(decl) &&
t.isIdentifier(decl.id) &&
t.isCallExpression(decl.init) &&
t.isIdentifier(decl.init.callee, { name: "useRef" }) &&
decl.id.name.includes(componentId)
) {
path.remove();
}
},
});
// 3. Boş satırları temizle
let output = generate(ast, { retainLines: true }).code;
output = output.replace(/\n{3,}/g, "\n\n");
return output;
}

View file

@ -1,675 +1,5 @@
import React, { useState, useEffect, useCallback, useRef } from 'react'
import { FaThLarge } from 'react-icons/fa'
import {
parseReactCode,
updateComponentProp,
updateComponentProps,
generateHookCode,
generateHookVariableName,
removeHookFromCode,
removeComponentAndHooksFromCode,
} from '../../utils/codeParser'
import { ComponentLibrary } from '../../components/codeLayout/ComponentLibrary'
import { Splitter } from '../../components/codeLayout/Splitter'
import { PanelManager } from '../../components/codeLayout/PanelManager'
import { ComponentDefinition, EditorState } from '../../proxy/developerKit/componentInfo'
import PropertyPanel from '../../components/codeLayout/PropertyPanel'
import ComponentSelector from '../../components/codeLayout/ComponentSelector'
import { useParams } from 'react-router-dom'
import { useComponents } from '../../contexts/ComponentContext'
import { Button, toast } from '../../components/ui'
import Notification from '../../components/ui/Notification/Notification'
import { PanelState } from '../../components/codeLayout/data/componentDefinitions'
import { ComponentCodeEditor } from '@/components/codeLayout/ComponentCodeEditor'
import VisualComponentDesigner from './VisualComponentDesigner'
const INITIAL_CODE = `const Component = () => {
return (
<>
</>
);
};
export default Component
`
function ComponentCodeLayout() {
const { id } = useParams()
const { getComponent, updateComponent } = useComponents()
const [showPanelManager, setShowPanelManager] = useState(false)
const [panelState, setPanelState] = useState<PanelState>({
toolbox: true,
properties: true,
})
const [editorState, setEditorState] = useState<EditorState>({
code: INITIAL_CODE,
components: [],
selectedComponentId: null,
})
const isEditing = !!id
const [code, setCode] = useState<string>(INITIAL_CODE)
const [isLoaded, setIsLoaded] = useState(false)
const parseDebounceRef = useRef<ReturnType<typeof setTimeout> | null>(null)
const [name, setName] = useState('')
const [dependencies, setDependencies] = useState<string[]>([])
const [isActive, setIsActive] = useState(true)
const handleSave = async () => {
try {
const componentData = {
name: name.trim(),
routePath: id ? (getComponent(id)?.routePath ?? '') : '',
dependencies: JSON.stringify(dependencies), // Serialize dependencies to JSON string
code: code.trim(),
isActive,
}
if (isEditing && id) {
updateComponent(id, componentData)
parseAndUpdateComponents(componentData.code)
}
} catch (error) {
console.error('Error saving component:', error)
alert('Failed to save component. Please try again.')
} finally {
setIsLoaded(true)
toast.push(
<Notification type="success" duration={2000}>
Bileşen başarıyla kaydedildi.
</Notification>,
{
placement: 'bottom-end',
},
)
}
}
// Load existing component data - sadece edit modunda
useEffect(() => {
if (isEditing && id && !isLoaded) {
const component = getComponent(id)
if (component) {
setName(component.name)
// setDescription(component.description || "");
// Parse dependencies from JSON string
try {
const deps = component.dependencies ? JSON.parse(component.dependencies) : []
setDependencies(Array.isArray(deps) ? deps : [])
} catch {
setDependencies([])
}
setCode(component.code) // Mevcut kodu yükle
// Parse components from the loaded code
parseAndUpdateComponents(component.code)
setIsActive(component.isActive)
setIsLoaded(true)
}
} else if (!isEditing && !isLoaded) {
// Yeni komponent için boş başla - TEMPLATE YOK
setIsLoaded(true)
}
}, [id, isEditing, getComponent, isLoaded])
const handleAppDragOver = (e: React.DragEvent<HTMLDivElement>) => {
e.preventDefault()
}
const parseAndUpdateComponents = useCallback((code: string) => {
try {
const parsed = parseReactCode(code)
setEditorState((prev) => ({
...prev,
code,
components: parsed.components,
}))
} catch (error) {
const msg = error instanceof Error ? error.message : 'Koddan komponentler parse edilemedi.'
console.log('Parse error:', msg)
setEditorState((prev) => ({
...prev,
code, // Only update the code, keep existing components
}))
}
}, [])
// Handle code changes from Monaco Editor
const handleCodeChange = useCallback(
(value: string | undefined) => {
if (value !== undefined) {
setCode(value)
}
},
[editorState.code],
)
// Apply code changes
const handleApplyCodeChanges = useCallback(() => {
parseAndUpdateComponents(code)
}, [parseAndUpdateComponents, code])
// Reset code changes
const handleResetCodeChanges = useCallback(() => {
setCode(editorState.code)
}, [editorState.code])
// Handle property changes from Property Panel
// Handle hook toggle
const handleHookToggle = useCallback(
(componentId: string, hookType: string, enabled: boolean) => {
console.log('🪝 App: handleHookToggle called:', {
componentId,
hookType,
enabled,
})
const selectedComponent = editorState.components?.find((c) => c.id === componentId)
if (!selectedComponent) return
// Use the most up-to-date code (pendingCode if available, otherwise editorState.code)
let updatedCode = code || editorState.code
console.log('🔍 App: Using code source:', code === editorState.code ? 'same' : 'pendingCode')
if (enabled) {
// Check if hook is already present - more specific check
const varName = generateHookVariableName(hookType, componentId)
console.log('🔍 App: Checking for existing hook variable:', varName)
// Create a more specific regex to check for actual hook declarations
const hookDeclarationRegex = new RegExp(
`const\\s+(?:\\[.*?${varName}.*?\\]|${varName})\\s*=\\s*${hookType}\\s*\\(`,
)
if (hookDeclarationRegex.test(updatedCode)) {
console.log('⚠️ App: Hook already exists, skipping')
return // Hook already exists
}
// Handle React imports - improved approach
const reactImportRegex = /^import\s+\{([^}]*)\}\s+from\s+['"]react['"]\s*;?/m
const reactMatch = reactImportRegex.exec(updatedCode)
if (reactMatch) {
// reactMatch[1] --> süslü parantez içi örn: "useState, useRef"
let existingHooks = reactMatch[1]
.split(',')
.map((h) => h.trim())
.filter((h) => h)
// Eğer hookType yoksa ekle
if (!existingHooks.includes(hookType)) {
existingHooks.push(hookType)
}
// Tekrarları kaldır (güvenlik için)
existingHooks = [...new Set(existingHooks)]
const newImport = `import { ${existingHooks.join(', ')} } from 'react';\n`
updatedCode = updatedCode.replace(reactImportRegex, newImport)
console.log('🔄 App: Updated existing React import:', newImport.trim())
} else {
// React import satırı yoksa ekle
const importLine = `import { ${hookType} } from 'react';\n\n`
updatedCode = importLine + updatedCode
console.log('🔄 App: Added new React import:', importLine.trim())
}
// Add hook declaration
const hookCode = generateHookCode(hookType, componentId, selectedComponent.type)
console.log('🔍 App: Generated hook code:', hookCode)
// Try multiple patterns for function declaration
const functionPatterns = [
/function\s+Component\s*\([^)]*\)\s*\{/, // function Component() {
/function\s+\w+\s*\([^)]*\)\s*\{/, // function AnyName() {
/const\s+\w+\s*=\s*\([^)]*\)\s*=>\s*\{/, // const Component = () => {
/export\s+default\s+function\s*\([^)]*\)\s*\{/, // export default function() {
]
let match2 = null
for (const pattern of functionPatterns) {
match2 = updatedCode.match(pattern)
if (match2) {
console.log('🔍 App: Found function with pattern:', pattern)
break
}
}
console.log('🔍 App: Function match result:', match2)
console.log('🔍 App: Updated code preview:', updatedCode.substring(0, 500))
if (match2 && typeof match2.index === 'number') {
const insertPosition = match2.index + match2[0].length
updatedCode =
updatedCode.slice(0, insertPosition) +
'\n ' +
hookCode +
'\n' +
updatedCode.slice(insertPosition)
console.log('✅ App: Hook code inserted successfully')
} else {
console.log('⚠️ App: Could not find function body to insert hook')
// Fallback: insert after the first opening brace
const firstBrace = updatedCode.indexOf('{')
if (firstBrace !== -1) {
updatedCode =
updatedCode.slice(0, firstBrace + 1) +
'\n ' +
hookCode +
'\n' +
updatedCode.slice(firstBrace + 1)
console.log('✅ App: Hook code inserted using fallback method')
}
}
// Update component properties if needed
if (hookType === 'useState') {
const setterName = `set${varName.charAt(0).toUpperCase() + varName.slice(1)}`
// Update component props based on type
if (selectedComponent.type === 'input') {
updatedCode = updateComponentProp(updatedCode, componentId, 'value', `{${varName}}`)
updatedCode = updateComponentProp(
updatedCode,
componentId,
'onChange',
`{(e) => ${setterName}(e.target.value)}`,
)
} else if (selectedComponent.type === 'button') {
updatedCode = updateComponentProp(
updatedCode,
componentId,
'onClick',
`{() => ${setterName}(!${varName})}`,
)
} else if (selectedComponent.type === 'checkbox') {
updatedCode = updateComponentProp(updatedCode, componentId, 'checked', `{${varName}}`)
updatedCode = updateComponentProp(
updatedCode,
componentId,
'onChange',
`{(val) => ${setterName}(val)}`,
)
}
} else if (hookType === 'useRef') {
updatedCode = updateComponentProp(updatedCode, componentId, 'ref', `{${varName}}`)
}
} else {
// Remove hook
updatedCode = removeHookFromCode(updatedCode, hookType, componentId)
// Remove related props
if (hookType === 'useState') {
if (selectedComponent.type === 'input') {
updatedCode = updateComponentProp(updatedCode, componentId, 'value', '')
updatedCode = updateComponentProp(updatedCode, componentId, 'onChange', null)
} else if (selectedComponent.type === 'button') {
updatedCode = updateComponentProp(updatedCode, componentId, 'onClick', null)
} else if (selectedComponent.type === 'checkbox') {
updatedCode = updateComponentProp(updatedCode, componentId, 'checked', false)
updatedCode = updateComponentProp(updatedCode, componentId, 'onChange', null)
}
} else if (hookType === 'useRef') {
updatedCode = updateComponentProp(updatedCode, componentId, 'ref', null)
}
}
setEditorState((prev) => {
console.log('🔄 App: Final updatedCode before parsing:', updatedCode)
const parsed = parseReactCode(updatedCode)
console.log('🔄 App: Parsed components:', parsed.components?.length)
const newState = {
code: updatedCode,
components: parsed.components,
selectedComponentId: prev.selectedComponentId, // Preserve selection
}
console.log('🔄 App: New editor state code preview:', newState.code.substring(0, 300))
return newState
})
// Also update pending code to match
setCode(updatedCode)
},
[editorState.code, editorState.components, code],
)
// Zincirleme hook güncelleme fonksiyonu
const applyMultipleHookToggles = (
toggles: { componentId: string; hookType: string; enabled: boolean }[],
) => {
let updatedCode = code || editorState.code
// 1. Sadece kaldırılması gereken hook'ları kaldır
toggles
.filter((t) => t.enabled === false)
.forEach(({ hookType, componentId }) => {
const selectedComponent = editorState.components?.find((c) => c.id === componentId)
if (!selectedComponent) return
updatedCode = removeHookFromCode(updatedCode, hookType, componentId)
// Prop temizliği
if (hookType === 'useState') {
if (selectedComponent.type === 'input') {
updatedCode = updateComponentProp(updatedCode, componentId, 'value', '')
updatedCode = updateComponentProp(updatedCode, componentId, 'onChange', null)
} else if (selectedComponent.type === 'button') {
updatedCode = updateComponentProp(updatedCode, componentId, 'onClick', null)
} else if (selectedComponent.type === 'checkbox') {
updatedCode = updateComponentProp(updatedCode, componentId, 'checked', false)
updatedCode = updateComponentProp(updatedCode, componentId, 'onChange', null)
}
} else if (hookType === 'useRef') {
updatedCode = updateComponentProp(updatedCode, componentId, 'ref', null)
}
})
// 2. Eklenmesi gereken hook'ları ekle (veya zaten varsa dokunma)
toggles
.filter((t) => t.enabled === true)
.forEach(({ hookType, componentId }) => {
const selectedComponent = editorState.components?.find((c) => c.id === componentId)
if (!selectedComponent) return
const varName = generateHookVariableName(hookType, componentId)
// Hook kodu fonksiyon gövdesinde yoksa ekle
const hookDeclarationRegex = new RegExp(
`const\\s+(?:\\[.*?${varName}.*?\\]|${varName})\\s*=\\s*${hookType}\\s*\\(`,
)
if (!hookDeclarationRegex.test(updatedCode)) {
const reactImportRegex =
// eslint-disable-next-line no-useless-escape
/import\\s+React(?:\\s*,\\s*\\{([^}]*)\\})?\\s+from\\s+['\"]react['\"];?/
const importMatch = updatedCode.match(reactImportRegex)
const allHooks = new Set<string>()
if (importMatch && importMatch[1]) {
importMatch[1]
.split(',')
.map((h) => h.trim())
.filter((h) => h)
.forEach((h) => allHooks.add(h))
}
allHooks.add(hookType)
let newImport = ''
if (allHooks.size > 0) {
newImport = `import { ${Array.from(allHooks).join(', ')} } from 'react';\n`
}
if (importMatch) {
updatedCode = updatedCode.replace(reactImportRegex, newImport)
} else {
updatedCode = newImport + updatedCode
}
// Hook kodunu fonksiyon gövdesine ekle
const hookCode = generateHookCode(hookType, componentId, selectedComponent.type)
const functionPatterns = [
/function\s+Component\s*\([^)]*\)\s*\{/, // function Component() {
/function\s+\w+\s*\([^)]*\)\s*\{/, // function AnyName() {
/const\s+\w+\s*=\s*\([^)]*\)\s*=>\s*\{/, // const Component = () => {
/export\s+default\s+function\s*\([^)]*\)\s*\{/, // export default function() {
]
let match2 = null
for (const pattern of functionPatterns) {
match2 = updatedCode.match(pattern)
if (match2) break
}
if (match2 && typeof match2.index === 'number') {
const insertPosition = match2.index + match2[0].length
updatedCode =
updatedCode.slice(0, insertPosition) +
'\n ' +
hookCode +
'\n' +
updatedCode.slice(insertPosition)
} else {
const firstBrace = updatedCode.indexOf('{')
if (firstBrace !== -1) {
updatedCode =
updatedCode.slice(0, firstBrace + 1) +
'\n ' +
hookCode +
'\n' +
updatedCode.slice(firstBrace + 1)
}
}
}
// Prop güncellemeleri
if (hookType === 'useState') {
const setterName = `set${varName.charAt(0).toUpperCase() + varName.slice(1)}`
if (selectedComponent.type === 'input') {
updatedCode = updateComponentProp(updatedCode, componentId, 'value', `{${varName}}`)
updatedCode = updateComponentProp(
updatedCode,
componentId,
'onChange',
`{(e) => ${setterName}(e.target.value)}`,
)
} else if (selectedComponent.type === 'button') {
updatedCode = updateComponentProp(
updatedCode,
componentId,
'onClick',
`{() => ${setterName}(!${varName})}`,
)
} else if (selectedComponent.type === 'checkbox') {
updatedCode = updateComponentProp(updatedCode, componentId, 'checked', `{${varName}}`)
updatedCode = updateComponentProp(
updatedCode,
componentId,
'onChange',
`{(e) => ${setterName}(e.target.checked)}`,
)
}
} else if (hookType === 'useRef') {
updatedCode = updateComponentProp(updatedCode, componentId, 'ref', `{${varName}}`)
}
})
setEditorState((prev) => {
const parsed = parseReactCode(updatedCode)
return {
code: updatedCode,
components: parsed.components,
selectedComponentId: prev.selectedComponentId,
}
})
setCode(updatedCode)
}
// Handle multiple property changes at once
const handlePropertiesChange = useCallback(
(componentId: string, updates: Record<string, any>) => {
console.log('🔄 App: handlePropertiesChange called:', {
componentId,
updates,
})
const updatedCode = updateComponentProps(editorState.code, componentId, updates)
console.log('📝 App: Properties updated, code changed:', editorState.code !== updatedCode)
setEditorState((prev) => {
const parsed = parseReactCode(updatedCode)
return {
code: updatedCode,
components: parsed.components,
selectedComponentId: prev.selectedComponentId, // Preserve selection
}
})
// Update pending code to reflect changes
setCode(updatedCode)
},
[editorState.code],
)
// Handle component list refresh
const handleRefreshComponents = useCallback(() => {
parseAndUpdateComponents(editorState.code)
}, [parseAndUpdateComponents, editorState.code])
// Handle component selection
const handleSelectComponent = useCallback((componentId: string | null) => {
setEditorState((prev) => ({
...prev,
selectedComponentId: componentId,
}))
}, [])
// Auto-parse ComponentSelector whenever code changes (debounced for live refresh)
useEffect(() => {
if (parseDebounceRef.current) {
clearTimeout(parseDebounceRef.current)
}
parseDebounceRef.current = setTimeout(() => {
parseAndUpdateComponents(code)
}, 500)
return () => {
if (parseDebounceRef.current) {
clearTimeout(parseDebounceRef.current)
}
}
}, [code, parseAndUpdateComponents])
const handleDragStart = (_componentDef: ComponentDefinition, e: React.DragEvent) => {
e.stopPropagation()
}
const selectedComponent =
editorState.components?.find((c) => c.id === editorState.selectedComponentId) || null
const renderLeftPanel = () => {
if (!panelState.toolbox) return null
return <ComponentLibrary onDragStart={handleDragStart} />
}
// Komponent ve ilgili hook'ları koddan silen fonksiyon
const handleDeleteComponent = (componentId: string) => {
// Koddan JSX ve hook'ları sil
const updatedCode = removeComponentAndHooksFromCode(code, componentId)
// Koddan parse edip state'i güncelle
parseAndUpdateComponents(updatedCode)
setCode(updatedCode)
// Seçili komponenti kaldır
setEditorState((prev) => ({
...prev,
selectedComponentId: null,
}))
}
const renderRightPanel = () => {
if (!panelState.properties) return null
return (
<div className="flex flex-col flex-1 min-h-0 h-full w-full">
<ComponentSelector
components={editorState.components}
selectedComponentId={editorState.selectedComponentId}
onSelectComponent={handleSelectComponent}
onRefresh={handleRefreshComponents}
/>
<PropertyPanel
selectedComponent={selectedComponent}
currentCode={editorState.code}
onPropertiesChange={handlePropertiesChange}
onHookToggle={handleHookToggle}
onMultipleHookToggle={applyMultipleHookToggles}
onDeleteComponent={handleDeleteComponent}
/>
</div>
)
}
const mainContent = (
<div className="flex-1 flex flex-col min-h-0 h-full">
{/* Top Header */}
<div className="bg-white dark:bg-gray-900 border-b border-gray-200 dark:border-gray-700 px-3 py-3">
<div className="flex items-center justify-between">
<div className="flex items-center space-x-4">
<h1 className="text-lg font-semibold text-gray-900 dark:text-gray-100">{name}</h1>
<p className="text-xs text-gray-500 dark:text-gray-400">{dependencies.join(', ')}</p>
</div>
<div className="flex items-center">
<Button
variant="default"
size="sm"
onClick={() => setShowPanelManager(true)}
className="!inline-flex items-center gap-2 rounded-lg border border-gray-300 !bg-white transition-colors hover:!bg-gray-50 dark:border-gray-700 dark:!bg-gray-900 dark:hover:!bg-gray-800"
title="Panel Manager"
>
<FaThLarge className="text-gray-600 dark:text-gray-300" />
<span className="text-sm text-gray-600 dark:text-gray-300">Panels</span>
</Button>
</div>
</div>
</div>
<div className="flex flex-col flex-1 min-h-0 h-full">
<div className="flex-1 h-full min-h-0">
<ComponentCodeEditor
code={code}
onChange={handleCodeChange}
onApplyCodeChanges={handleApplyCodeChanges}
onResetCodeChanges={handleResetCodeChanges}
onComponentSave={handleSave}
/>
</div>
</div>
</div>
)
return (
<div
className="flex h-screen bg-gray-50 dark:bg-gray-950"
onDragOver={handleAppDragOver}
onDrop={(e) => {
e.preventDefault()
e.stopPropagation()
}}
>
{/* Panel Yöneticisi Modal */}
{showPanelManager && (
<PanelManager
isOpen={showPanelManager}
onClose={() => setShowPanelManager(false)}
panelState={panelState}
onPanelToggle={(panel) => setPanelState((prev) => ({ ...prev, [panel]: !prev[panel] }))}
/>
)}
{/* Sol Sidebar ve ana içerik */}
{panelState.toolbox ? (
<Splitter direction="horizontal" initialSize={288} minSize={200} maxSize={400}>
{renderLeftPanel()}
<div className="flex flex-1">
{panelState.properties ? (
<Splitter
direction="horizontal"
initialSize={400}
minSize={300}
maxSize={window.innerWidth - 288 - 100}
reverse={true}
>
{mainContent}
{renderRightPanel()}
</Splitter>
) : (
mainContent
)}
</div>
</Splitter>
) : (
<div className="flex flex-1">
{panelState.properties ? (
<Splitter
direction="horizontal"
initialSize={400}
minSize={300}
maxSize={window.innerWidth - 100}
reverse={true}
>
{mainContent}
{renderRightPanel()}
</Splitter>
) : (
mainContent
)}
</div>
)}
</div>
)
}
export default ComponentCodeLayout
// Kept as the route entry point for existing ABP menu records. The designer itself
// lives in a separate component so the visual schema/runtime can evolve independently.
export default VisualComponentDesigner

View file

@ -8,6 +8,12 @@ import { useLocalization } from '@/utils/hooks/useLocalization'
import { Formik, Form, Field } from 'formik'
import * as Yup from 'yup'
import { Button, Checkbox, FormContainer, FormItem, Input } from '@/components/ui'
import { generateDesignerCode } from '@/components/visualDesigner/codeGenerator'
import {
createDesignerId,
createEmptyDesignerDocument,
type DesignerDocument,
} from '@/components/visualDesigner/types'
// Validation schema
const validationSchema = Yup.object({
@ -15,7 +21,6 @@ const validationSchema = Yup.object({
routePath: Yup.string().required().matches(/^\//, 'Route path must start with /'),
description: Yup.string(),
dependencies: Yup.array().of(Yup.string()),
code: Yup.string(),
isActive: Yup.boolean(),
})
@ -36,7 +41,6 @@ const ComponentEditor: React.FC = () => {
routePath: '',
description: '',
dependencies: [] as string[],
code: '',
isActive: true,
})
@ -59,7 +63,6 @@ const ComponentEditor: React.FC = () => {
routePath: component.routePath,
description: component.description || '',
dependencies: deps,
code: component.code,
isActive: component.isActive,
}
@ -72,22 +75,20 @@ const ComponentEditor: React.FC = () => {
}
}, [id, isEditing, getComponent, isLoaded])
// Generate component template based on name
const generateComponentTemplate = (componentName: string): string => {
if (!componentName.trim()) return ''
// Convert component name to PascalCase if not already
const pascalCaseName = componentName.charAt(0).toUpperCase() + componentName.slice(1)
return `const ${pascalCaseName}Component = ({
title = "${pascalCaseName}"
}) => {
return (
<span>{title}</span>
);
};
export default ${pascalCaseName}Component;`
const createDefaultVisualDocument = (componentName: string): DesignerDocument => {
const document = createEmptyDesignerDocument('visual')
document.nodes = [
{
id: createDesignerId(),
type: 'span',
kind: 'html',
props: { children: componentName.trim() },
events: {},
bindings: {},
children: [],
},
]
return document
}
const handleSubmit = async (values: typeof initialValues, { setSubmitting }: any) => {
@ -98,12 +99,19 @@ export default ${pascalCaseName}Component;`
}
try {
const existingComponent = isEditing && id ? getComponent(id) : undefined
const defaultDocument = createDefaultVisualDocument(values.name)
const componentData = {
name: values.name.trim(),
routePath: values.routePath.trim(),
description: values.description.trim(),
dependencies: JSON.stringify(values.dependencies), // Serialize dependencies to JSON string
code: values.code.trim(),
code: existingComponent
? existingComponent.code
: generateDesignerCode(values.name, defaultDocument),
props: existingComponent
? existingComponent.props
: JSON.stringify({ visualDesigner: defaultDocument }),
isActive: values.isActive,
}
@ -188,9 +196,7 @@ export default ${pascalCaseName}Component;`
onClick={submitForm}
>
<FaRegSave className="w-4 h-4" />
{isSubmitting
? translate('::Saving')
: translate('::Save')}
{isSubmitting ? translate('::Saving') : translate('::Save')}
</Button>
</div>
</div>
@ -222,24 +228,7 @@ export default ${pascalCaseName}Component;`
component={Input}
placeholder="e.g., Button, Card, Modal"
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
const newName = e.target.value
setFieldValue('name', newName)
// Auto-generate code template if this is a new component (not editing)
// and only if the code field is empty or contains the default template
if (!isEditing && newName.trim()) {
const currentCode = values.code.trim()
const isCodeEmpty = !currentCode
const isCodeDefaultTemplate =
currentCode.includes('Component = ({') &&
currentCode.includes('export default') &&
currentCode.includes('<span>{title}</span>')
if (isCodeEmpty || isCodeDefaultTemplate) {
const template = generateComponentTemplate(newName)
setFieldValue('code', template)
}
}
setFieldValue('name', e.target.value)
}}
/>
</FormItem>
@ -270,24 +259,6 @@ export default ${pascalCaseName}Component;`
/>
</FormItem>
<FormItem
label={translate('::App.Platform.Code')}
invalid={!!(errors.code && touched.code)}
errorMessage={errors.code as string}
>
<Field
name="code"
type="text"
component={Input}
placeholder="React component code goes here"
textArea={true}
rows={10}
onChange={(e: React.ChangeEvent<HTMLTextAreaElement>) => {
setFieldValue('code', e.target.value)
}}
/>
</FormItem>
<FormItem
label={translate('::App.DeveloperKit.ComponentEditor.Dependencies')}
invalid={!!(errors.dependencies && touched.dependencies)}

View file

@ -1,4 +1,4 @@
import React, { useState } from 'react'
import React, { useEffect, useState } from 'react'
import { Link, useNavigate } from 'react-router-dom'
import { useComponents } from '../../contexts/ComponentContext'
import {
@ -10,7 +10,8 @@ import {
FaEyeSlash,
FaFilter,
FaCalendarAlt,
FaExternalLinkAlt,
FaRegWindowMaximize,
FaSyncAlt,
} from 'react-icons/fa'
import Widget from '@/components/common/Widget'
import { ROUTES_ENUM } from '@/routes/route.constant'
@ -21,10 +22,12 @@ import { Helmet } from 'react-helmet'
import { Button } from '@/components/ui'
const ComponentManager: React.FC = () => {
const { components, loading, updateComponent, deleteComponent } = useComponents()
const { components, loading, updateComponent, deleteComponent, refreshComponents } =
useComponents()
const navigate = useNavigate()
const [searchTerm, setSearchTerm] = useState('')
const [filterActive, setFilterActive] = useState<'all' | 'active' | 'inactive'>('all')
const [isRefreshing, setIsRefreshing] = useState(false)
// Calculate statistics
const totalComponents = components?.length || 0
@ -32,6 +35,19 @@ const ComponentManager: React.FC = () => {
const inactiveComponents = totalComponents - activeComponents
const { translate } = useLocalization()
useEffect(() => {
void refreshComponents()
}, [refreshComponents])
const handleRefresh = async () => {
setIsRefreshing(true)
try {
await refreshComponents()
} finally {
setIsRefreshing(false)
}
}
const filteredComponents = components?.filter((component) => {
const matchesSearch =
component.name.toLowerCase().includes(searchTerm.toLowerCase()) ||
@ -68,7 +84,7 @@ const ComponentManager: React.FC = () => {
const handleOpenRoute = (routePath: string) => {
const normalizedPath = routePath.startsWith('/') ? routePath : `/${routePath}`
navigate(normalizedPath)
window.open(normalizedPath, '_blank', 'noopener,noreferrer')
}
return (
@ -114,16 +130,16 @@ const ComponentManager: React.FC = () => {
type="text"
placeholder={translate('::App.DeveloperKit.Component.SearchPlaceholder')}
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
className="w-full pl-10 pr-2 py-1 border border-slate-300 dark:border-gray-700 rounded-lg bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 focus:ring-2 focus:ring-blue-500 focus:border-transparent"
onChange={(e) => setSearchTerm(e.target.value)}
/>
</div>
<div className="flex items-center gap-2">
<FaFilter className="w-5 h-5 text-slate-500 dark:text-gray-400" />
<select
value={filterActive}
onChange={(e) => setFilterActive(e.target.value as 'all' | 'active' | 'inactive')}
className="px-2 py-1 border border-slate-300 dark:border-gray-700 rounded-lg bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 focus:ring-2 focus:ring-blue-500 focus:border-transparent"
onChange={(e) => setFilterActive(e.target.value as 'all' | 'active' | 'inactive')}
>
<option value="all">{translate('::App.DeveloperKit.Component.Filter.All')}</option>
<option value="active">
@ -134,6 +150,16 @@ const ComponentManager: React.FC = () => {
</option>
</select>
</div>
<Button
className="flex items-center gap-2"
disabled={loading || isRefreshing}
type="button"
variant="default"
onClick={() => void handleRefresh()}
>
<FaSyncAlt className={isRefreshing ? 'animate-spin' : ''} />
Yenile
</Button>
<div>
<Button
type="button"
@ -211,18 +237,6 @@ const ComponentManager: React.FC = () => {
)}
</div>
{/* Props Preview */}
{component.props && (
<div className="mb-4">
<p className="text-xs font-medium text-slate-700 dark:text-gray-400 mb-1">
{translate('::App.DeveloperKit.Component.PropsLabel')}
</p>
<code className="text-xs bg-slate-100 dark:bg-gray-800 text-slate-600 dark:text-gray-300 px-2 py-1 rounded">
{component.props}
</code>
</div>
)}
{/* Actions */}
<div className="flex items-center justify-between pt-2 border-t border-slate-100 dark:border-gray-700">
<div className="flex items-center gap-2">
@ -230,8 +244,8 @@ const ComponentManager: React.FC = () => {
type="button"
variant="plain"
shape="circle"
onClick={() => handleToggleActive(component.id, !component.isActive)}
className={`!inline-flex !h-auto items-center gap-1 rounded !px-2 py-1 text-xs font-medium transition-colors`}
onClick={() => handleToggleActive(component.id, !component.isActive)}
>
{component.isActive ? (
<>
@ -250,19 +264,8 @@ const ComponentManager: React.FC = () => {
<Button
type="button"
variant="solid"
shape="circle"
color="gray-600"
disabled={!component.isActive || !component.routePath?.trim()}
title={translate('::OpenUrl')}
onClick={() => handleOpenRoute(component.routePath)}
>
<FaExternalLinkAlt className="w-3 h-3" />
</Button>
<Button
type="button"
variant="solid"
shape="circle"
color="blue-600"
title={translate('::Edit')}
onClick={() =>
window.open(
ROUTES_ENUM.protected.saas.developerKit.componentsEdit.replace(
@ -272,15 +275,24 @@ const ComponentManager: React.FC = () => {
'_blank',
)
}
title={translate('::Edit')}
>
<FaRegEdit className="w-3 h-3" />
<FaRegEdit className="w-4 h-4" />
</Button>
<Button
type="button"
variant="solid"
color="gray-600"
disabled={!component.isActive || !component.routePath?.trim()}
title={translate('::OpenUrl')}
onClick={() => handleOpenRoute(component.routePath)}
>
<FaRegWindowMaximize className="w-4 h-4" />
</Button>
<Button
type="button"
variant="solid"
shape="circle"
color="green-600"
title={translate('::App.Platform.View')}
onClick={() =>
navigate(
ROUTES_ENUM.protected.saas.developerKit.componentsView.replace(
@ -289,19 +301,17 @@ const ComponentManager: React.FC = () => {
),
)
}
title={translate('::App.Platform.View')}
>
<FaEye className="w-3 h-3" />
<FaEye className="w-4 h-4" />
</Button>
<Button
type="button"
variant="solid"
shape="circle"
color="red-600"
onClick={() => handleDelete(component.id)}
title={translate('::Delete')}
onClick={() => handleDelete(component.id)}
>
<FaTrashAlt className="w-3 h-3" />
<FaTrashAlt className="w-4 h-4" />
</Button>
</div>
</div>

File diff suppressed because it is too large Load diff

View file

@ -37,10 +37,20 @@ import { useFilters } from './useFilters'
import GridFilterDialogs from './GridFilterDialogs'
type GanttInstance = ReturnType<GanttRef['instance']>
type GanttTreeListInstance = {
element: () => HTMLElement
getVisibleColumns: () => Array<Record<string, any>>
state: {
(): Record<string, any>
(value: Record<string, any> | null): void
}
}
const getGanttTreeList = (gantt: GanttInstance) => {
const element = (gantt.element() as HTMLElement).querySelector('.dx-treelist')
return element ? TreeList.getInstance(element as HTMLElement) : undefined
return element
? (TreeList.getInstance(element as HTMLElement) as unknown as GanttTreeListInstance)
: undefined
}
interface GanttViewProps {
@ -161,7 +171,7 @@ const GanttView = (props: GanttViewProps) => {
)
setGanttDataSource(dataSource)
}, [gridDto, searchParams, createSelectDataSource])
}, [createSelectDataSource, getBandedColumns, gridDto, layout, listFormCode, searchParams])
useEffect(() => {
refListFormCode.current = listFormCode
@ -193,7 +203,7 @@ const GanttView = (props: GanttViewProps) => {
ROUTES_ENUM.protected.saas.listFormManagement.edit.replace(':listFormCode', listFormCode),
isPwaMode ? '_self' : '_blank',
)
}, [])
}, [isPwaMode, listFormCode])
const getSettingButtonOptions = useCallback(
() => ({
@ -217,7 +227,7 @@ const GanttView = (props: GanttViewProps) => {
gridRef.current?.instance()?.refresh()
},
}),
[settingButtonClick],
[translate],
)
const getCurrentState = useCallback((gantt: GanttInstance) => {
@ -248,7 +258,7 @@ const GanttView = (props: GanttViewProps) => {
showDependencies: gantt.option('showDependencies'),
showResources: gantt.option('showResources'),
taskListWidth: Math.round(
taskListElement?.getBoundingClientRect().width ?? gantt.option('taskListWidth'),
taskListElement?.getBoundingClientRect().width ?? gantt.option('taskListWidth') ?? 500,
),
}
}, [])
@ -510,7 +520,7 @@ const GanttView = (props: GanttViewProps) => {
allowResourceDeleting={gridDto.gridOptions.ganttOptionDto?.allowResourceDeleting}
allowResourceUpdating={gridDto.gridOptions.ganttOptionDto?.allowResourceUpdating}
/>
<FilterRow visible={gridDto.gridOptions.filterRowDto?.visible}></FilterRow>
<HeaderFilter visible={gridDto.gridOptions.headerFilterDto.visible}></HeaderFilter>
<Sorting mode={gridDto.gridOptions?.sortMode}></Sorting>

View file

@ -314,7 +314,7 @@ const Grid = (props: GridProps) => {
const { customSaveState, customLoadState } = useListFormStateStoring({
listFormCode,
storageKey: gridDto?.gridOptions.stateStoringDto?.storageKey,
storageKey: gridDto?.gridOptions?.stateStoringDto?.storageKey,
filterPrefix: 'list',
skipSaveRef: isEditingRef,
})

View file

@ -81,8 +81,19 @@ function isLayoutValid(dto: GridDto, layout: ListViewLayoutType | undefined): bo
return false
}
const List: React.FC = () => {
const { listFormCode = '' } = useParams()
interface ListProps {
listFormCode?: string
gridDto?: GridDto
onRefreshGridDto?: () => Promise<void> | void
}
const List: React.FC<ListProps> = ({
listFormCode: externalListFormCode,
gridDto: externalGridDto,
onRefreshGridDto,
}) => {
const { listFormCode: routeListFormCode = '' } = useParams()
const listFormCode = externalListFormCode || routeListFormCode
const [searchParams] = useSearchParams()
const { translate } = useLocalization()
@ -99,7 +110,9 @@ const List: React.FC = () => {
listFormCode: string
gridDto: GridDto | null
}>({ listFormCode: '', gridDto: null })
const gridDto = gridDtoState.listFormCode === listFormCode ? gridDtoState.gridDto : null
const gridDto =
externalGridDto ??
(gridDtoState.listFormCode === listFormCode ? gridDtoState.gridDto : null)
const [viewModeState, setViewModeState] = useState<{
listFormCode: string
@ -112,6 +125,12 @@ const List: React.FC = () => {
======================= */
const refreshGridDto = useCallback(async () => {
if (!listFormCode) return
if (onRefreshGridDto) {
await onRefreshGridDto()
return
}
const requestedListFormCode = listFormCode
try {
const response = await getList({ listFormCode: requestedListFormCode })
@ -120,11 +139,11 @@ const List: React.FC = () => {
} catch (err) {
console.error('GridDto load error:', err)
}
}, [listFormCode])
}, [listFormCode, onRefreshGridDto])
useEffect(() => {
refreshGridDto()
}, [refreshGridDto])
if (!externalGridDto) void refreshGridDto()
}, [externalGridDto, refreshGridDto])
/* =======================
🔹 INITIAL VIEW MODE