Custom Component düzenlemesi
This commit is contained in:
parent
9cc77fb3a1
commit
25ae424f42
40 changed files with 6852 additions and 4592 deletions
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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 }";
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
|
|
@ -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),
|
||||
|
|
@ -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
3
ui/.gitignore
vendored
|
|
@ -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
14
ui/package-lock.json
generated
|
|
@ -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": [
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
182
ui/scripts/generate-component-metadata.mjs
Normal file
182
ui/scripts/generate-component-metadata.mjs
Normal 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.`)
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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">Açı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">Açı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>
|
||||
)
|
||||
}
|
||||
|
|
@ -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>
|
||||
);
|
||||
};
|
||||
|
|
@ -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;
|
||||
|
|
@ -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>
|
||||
);
|
||||
};
|
||||
|
|
@ -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;
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 : [];
|
||||
};
|
||||
|
|
@ -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
|
||||
|
|
@ -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"
|
||||
);
|
||||
}
|
||||
|
|
@ -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]
|
||||
|
||||
|
|
|
|||
114
ui/src/components/componentEditor/PlatformViewHost.tsx
Normal file
114
ui/src/components/componentEditor/PlatformViewHost.tsx
Normal 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 property’sini 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
|
||||
|
|
@ -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}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
892
ui/src/components/visualDesigner/VisualCanvas.tsx
Normal file
892
ui/src/components/visualDesigner/VisualCanvas.tsx
Normal 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 layout’unu seçin
|
||||
</h3>
|
||||
<p className="mt-1 text-sm text-slate-500">
|
||||
Hazır bir yerleşimle başlayın veya Toolbox’taki 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
|
||||
475
ui/src/components/visualDesigner/catalog.ts
Normal file
475
ui/src/components/visualDesigner/catalog.ts
Normal 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 component’i`,
|
||||
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]
|
||||
}
|
||||
415
ui/src/components/visualDesigner/codeGenerator.ts
Normal file
415
ui/src/components/visualDesigner/codeGenerator.ts
Normal 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`
|
||||
}
|
||||
153
ui/src/components/visualDesigner/types.ts
Normal file
153
ui/src/components/visualDesigner/types.ts
Normal 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)
|
||||
}
|
||||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
3423
ui/src/views/developerKit/VisualComponentDesigner.tsx
Normal file
3423
ui/src/views/developerKit/VisualComponentDesigner.tsx
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
})
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Reference in a new issue