+ ${closeButton}
+ ${titleHtml}
+
${message}
+
`;
+
+ // Add event listeners
+ const closeButtonElement = toast.querySelector('.abp-toast-close-button');
+ if (closeButtonElement) {
+ closeButtonElement.addEventListener('click', () => this.remove(toast));
+ }
+
+ if (options.tapToDismiss) {
+ toast.addEventListener('click', () => this.remove(toast));
+ }
+
+ return toast;
+};
+
+// Show a toast with given options
+AbpToastService.prototype.show = function(message, title, severity = 'neutral', options = {}) {
+ const mergedOptions = this.extend({}, this.globalOptions, options);
+ const id = ++this.lastId;
+ const toast = this.createToastElement(message, title, severity, mergedOptions);
+
+ // Set data attributes for non-object options
+ Object.entries(mergedOptions)
+ .filter(([_, value]) => typeof value !== 'object')
+ .forEach(([key, value]) => toast.dataset[key] = value);
+
+ toast.dataset.id = id;
+ this.container.appendChild(toast);
+ this.toasts.push(toast);
+
+ // Auto remove if not sticky
+ if (!mergedOptions.sticky) {
+ setTimeout(() => this.remove(toast), mergedOptions.life);
+ }
+
+ return id;
+};
+
+// Remove a toast with animation
+AbpToastService.prototype.remove = function(toastElement) {
+ toastElement.classList.add('toast-removing');
+ setTimeout(() => {
+ if (toastElement.parentNode === this.container) {
+ this.container.removeChild(toastElement);
+ }
+ this.toasts = this.toasts.filter(t => t !== toastElement);
+ }, 300); // Match animation duration
+};
+
+// Convenience methods for different severities
+AbpToastService.prototype.success = function(message, title, options) {
+ return this.show(message, title, 'success', options);
+};
+
+AbpToastService.prototype.error = function(message, title, options) {
+ return this.show(message, title, 'error', options);
+};
+
+AbpToastService.prototype.info = function(message, title, options) {
+ return this.show(message, title, 'info', options);
+};
+
+AbpToastService.prototype.warning = function(message, title, options) {
+ return this.show(message, title, 'warning', options);
+};
+
+// Clear all toasts
+AbpToastService.prototype.clear = function(containerKey) {
+ if (containerKey) {
+ this.toasts = this.toasts.filter(toast => {
+ const shouldRemove = toast.dataset.containerKey === containerKey;
+ if (shouldRemove) {
+ this.remove(toast);
+ }
+ return !shouldRemove;
+ });
+ } else {
+ this.toasts.forEach(toast => this.remove(toast));
+ }
+};
+
+// Static method to set default options for all instances
+AbpToastService.setDefaultOptions = function(options) {
+ AbpToastService.defaultOptions = this.prototype.extend({}, AbpToastService.defaultOptions, options);
+};
+
+var abp = abp || {};
+(function () {
+ abp.notify.success = function (message, title, options) {
+ new AbpToastService().success(message, title, options);
+ };
+
+ abp.notify.info = function (message, title, options) {
+ new AbpToastService().info(message, title, options);
+ };
+
+ abp.notify.warn = function (message, title, options) {
+ new AbpToastService().warning(message, title, options);
+ };
+
+ abp.notify.error = function (message, title, options) {
+ new AbpToastService().error(message, title, options);
+ };
+})();
\ No newline at end of file
diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/wwwroot/libs/abp/aspnetcore-mvc-ui-theme-shared/toastr/abp-toastr.js b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/wwwroot/libs/abp/aspnetcore-mvc-ui-theme-shared/toastr/abp-toastr.js
deleted file mode 100644
index 37a7c40166..0000000000
--- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/wwwroot/libs/abp/aspnetcore-mvc-ui-theme-shared/toastr/abp-toastr.js
+++ /dev/null
@@ -1,34 +0,0 @@
-var abp = abp || {};
-(function () {
-
- if (!toastr) {
- return;
- }
-
- /* DEFAULTS *************************************************/
-
- toastr.options.positionClass = 'toast-bottom-right';
-
- /* NOTIFICATION *********************************************/
-
- var showNotification = function (type, message, title, options) {
- toastr[type](message, title, options);
- };
-
- abp.notify.success = function (message, title, options) {
- showNotification('success', message, title, options);
- };
-
- abp.notify.info = function (message, title, options) {
- showNotification('info', message, title, options);
- };
-
- abp.notify.warn = function (message, title, options) {
- showNotification('warning', message, title, options);
- };
-
- abp.notify.error = function (message, title, options) {
- showNotification('error', message, title, options);
- };
-
-})();
\ No newline at end of file
diff --git a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/ChangeTrackers/AbpEfCoreNavigationHelper.cs b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/ChangeTrackers/AbpEfCoreNavigationHelper.cs
index d56660be57..63ddc222bd 100644
--- a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/ChangeTrackers/AbpEfCoreNavigationHelper.cs
+++ b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/ChangeTrackers/AbpEfCoreNavigationHelper.cs
@@ -31,6 +31,14 @@ public class AbpEfCoreNavigationHelper : ITransientDependency
protected virtual void EntityEntryTrackedOrStateChanged(EntityEntry entityEntry)
{
+ if (entityEntry.State is EntityState.Unchanged or EntityState.Modified)
+ {
+ foreach (var entry in EntityEntries.Values.Where(x => x.NavigationEntries.Any()))
+ {
+ entry.UpdateNavigationEntries();
+ }
+ }
+
if (entityEntry.State != EntityState.Unchanged)
{
return;
@@ -189,6 +197,22 @@ public class AbpEfCoreNavigationHelper : ITransientDependency
return navigationEntryProperty != null && navigationEntryProperty.IsModified;
}
+ public virtual AbpNavigationEntry? GetNavigationEntry(EntityEntry entityEntry, int navigationEntryIndex)
+ {
+ var entryId = GetEntityEntryIdentity(entityEntry);
+ if (entryId == null)
+ {
+ return null;
+ }
+
+ if (!EntityEntries.TryGetValue(entryId, out var abpEntityEntry))
+ {
+ return null;
+ }
+
+ return abpEntityEntry.NavigationEntries.ElementAtOrDefault(navigationEntryIndex);
+ }
+
protected virtual string? GetEntityEntryIdentity(EntityEntry entityEntry)
{
if (entityEntry.Entity is IEntity entryEntity && entryEntity.GetKeys().Length == 1)
diff --git a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/ChangeTrackers/AbpEntityEntry.cs b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/ChangeTrackers/AbpEntityEntry.cs
index 0aef48e0d1..4b8a531db1 100644
--- a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/ChangeTrackers/AbpEntityEntry.cs
+++ b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/ChangeTrackers/AbpEntityEntry.cs
@@ -1,3 +1,4 @@
+using System.Collections;
using System.Collections.Generic;
using System.Linq;
using Microsoft.EntityFrameworkCore;
@@ -32,6 +33,49 @@ public class AbpEntityEntry
EntityEntry = entityEntry;
NavigationEntries = EntityEntry.Navigations.Select(x => new AbpNavigationEntry(x, x.Metadata.Name)).ToList();
}
+
+ public void UpdateNavigationEntries()
+ {
+ foreach (var navigationEntry in NavigationEntries)
+ {
+ if (IsModified ||
+ EntityEntry.State == EntityState.Modified ||
+ navigationEntry.IsModified ||
+ navigationEntry.NavigationEntry.IsModified)
+ {
+ continue;
+ }
+
+ var navigation = EntityEntry.Navigations.FirstOrDefault(n => n.Metadata.Name == navigationEntry.Name);
+
+ var currentValue = AbpNavigationEntry.GetOriginalValue(navigation?.CurrentValue);
+ if (currentValue == null)
+ {
+ continue;
+ }
+
+ switch (navigationEntry.OriginalValue)
+ {
+ case null:
+ navigationEntry.OriginalValue = currentValue;
+ break;
+ case IEnumerable originalValueCollection when currentValue is IEnumerable currentValueCollection:
+ {
+ var existingList = originalValueCollection.Cast