mirror of https://github.com/abpframework/abp.git
10 changed files with 377 additions and 84 deletions
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 77 KiB |
@ -1,15 +0,0 @@ |
|||||
using System.Collections.Generic; |
|
||||
using Volo.Abp.AspNetCore.Mvc.UI.Bundling; |
|
||||
using Volo.Abp.AspNetCore.Mvc.UI.Packages.JQuery; |
|
||||
using Volo.Abp.Modularity; |
|
||||
|
|
||||
namespace Volo.Abp.AspNetCore.Mvc.UI.Packages.Toastr; |
|
||||
|
|
||||
[DependsOn(typeof(JQueryScriptContributor))] |
|
||||
public class ToastrScriptBundleContributor : BundleContributor |
|
||||
{ |
|
||||
public override void ConfigureBundle(BundleConfigurationContext context) |
|
||||
{ |
|
||||
context.Files.AddIfNotContains("/libs/toastr/toastr.min.js"); |
|
||||
} |
|
||||
} |
|
||||
@ -1,12 +0,0 @@ |
|||||
using System.Collections.Generic; |
|
||||
using Volo.Abp.AspNetCore.Mvc.UI.Bundling; |
|
||||
|
|
||||
namespace Volo.Abp.AspNetCore.Mvc.UI.Packages.Toastr; |
|
||||
|
|
||||
public class ToastrStyleBundleContributor : BundleContributor |
|
||||
{ |
|
||||
public override void ConfigureBundle(BundleConfigurationContext context) |
|
||||
{ |
|
||||
context.Files.AddIfNotContains("/libs/toastr/toastr.min.css"); |
|
||||
} |
|
||||
} |
|
||||
@ -0,0 +1,139 @@ |
|||||
|
.abp-toast-container { |
||||
|
position: fixed; |
||||
|
display: flex; |
||||
|
flex-direction: column; |
||||
|
align-items: center; |
||||
|
justify-content: flex-end; |
||||
|
min-width: 350px; |
||||
|
min-height: 80px; |
||||
|
z-index: 1900; |
||||
|
right: 30px; |
||||
|
bottom: 30px; |
||||
|
} |
||||
|
|
||||
|
.abp-toast { |
||||
|
display: grid; |
||||
|
grid-template-columns: 35px 1fr; |
||||
|
gap: 5px; |
||||
|
margin: 5px 0; |
||||
|
padding: 7px; |
||||
|
width: 350px; |
||||
|
user-select: none; |
||||
|
z-index: 9999; |
||||
|
color: #fff; |
||||
|
border-radius: 8px; |
||||
|
font-size: 14px; |
||||
|
box-shadow: 0 0 20px 0 rgba(76, 87, 125, 0.02); |
||||
|
animation: toastIn 0.3s ease-in-out; |
||||
|
} |
||||
|
|
||||
|
.abp-toast-success { |
||||
|
border: 2px solid #4fbf67; |
||||
|
background-color: #4fbf67; |
||||
|
} |
||||
|
|
||||
|
.abp-toast-error { |
||||
|
border: 2px solid #c00d49; |
||||
|
background-color: #c00d49; |
||||
|
} |
||||
|
|
||||
|
.abp-toast-info { |
||||
|
border: 2px solid #438aa7; |
||||
|
background-color: #438aa7; |
||||
|
} |
||||
|
|
||||
|
.abp-toast-warning { |
||||
|
border: 2px solid #ff9f38; |
||||
|
background-color: #ff9f38; |
||||
|
} |
||||
|
|
||||
|
.abp-toast-icon { |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
justify-content: center; |
||||
|
} |
||||
|
|
||||
|
.abp-toast-icon .icon { |
||||
|
font-size: 32px; |
||||
|
} |
||||
|
|
||||
|
.abp-toast-content { |
||||
|
position: relative; |
||||
|
display: flex; |
||||
|
align-self: center; |
||||
|
flex-direction: column; |
||||
|
word-break: break-word; |
||||
|
padding-bottom: 2px; |
||||
|
} |
||||
|
|
||||
|
.abp-toast-close-button { |
||||
|
position: absolute; |
||||
|
top: 0; |
||||
|
right: 0; |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
justify-content: center; |
||||
|
margin: 0; |
||||
|
padding: 0px 5px 0 0; |
||||
|
width: 25px; |
||||
|
height: 100%; |
||||
|
border: none; |
||||
|
border-radius: 50%; |
||||
|
background: transparent; |
||||
|
color: inherit; |
||||
|
cursor: pointer; |
||||
|
} |
||||
|
|
||||
|
.abp-toast-close-button:focus { |
||||
|
outline: none; |
||||
|
} |
||||
|
|
||||
|
.abp-toast-title { |
||||
|
margin: 0; |
||||
|
padding: 0; |
||||
|
font-size: 1rem; |
||||
|
font-weight: 600; |
||||
|
} |
||||
|
|
||||
|
.abp-toast-message { |
||||
|
margin: 0; |
||||
|
padding: 0; |
||||
|
max-width: 240px; |
||||
|
} |
||||
|
|
||||
|
@keyframes toastIn { |
||||
|
from { |
||||
|
transform: translateX(100%); |
||||
|
opacity: 0; |
||||
|
} |
||||
|
to { |
||||
|
transform: translateX(0); |
||||
|
opacity: 1; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
@keyframes toastOut { |
||||
|
from { |
||||
|
transform: translateX(0); |
||||
|
opacity: 1; |
||||
|
} |
||||
|
to { |
||||
|
transform: translateX(100%); |
||||
|
opacity: 0; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
.toast-removing { |
||||
|
animation: toastOut 0.3s ease-in-out forwards; |
||||
|
} |
||||
|
|
||||
|
@media only screen and (max-width: 768px) { |
||||
|
.abp-toast-container { |
||||
|
min-width: 100%; |
||||
|
right: 0; |
||||
|
} |
||||
|
|
||||
|
.abp-toast { |
||||
|
width: 95%; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,216 @@ |
|||||
|
function AbpToastService(globalOptions) { |
||||
|
// Static default configuration for all instances
|
||||
|
if (!AbpToastService.defaultOptions) { |
||||
|
AbpToastService.defaultOptions = { |
||||
|
closable: true, |
||||
|
sticky: false, |
||||
|
life: 5000, |
||||
|
tapToDismiss: false, |
||||
|
containerKey: undefined, |
||||
|
iconClass: undefined, |
||||
|
position: { |
||||
|
top: 'auto', |
||||
|
right: '30px', |
||||
|
bottom: '30px', |
||||
|
left: 'auto' |
||||
|
} |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
// Find existing container or create new one
|
||||
|
const containerId = globalOptions?.containerKey ? |
||||
|
`toast-container-${globalOptions.containerKey}` : |
||||
|
'toast-container'; |
||||
|
|
||||
|
this.container = document.getElementById(containerId); |
||||
|
if (!this.container) { |
||||
|
this.container = document.createElement('div'); |
||||
|
this.container.id = containerId; |
||||
|
this.container.className = 'abp-toast-container'; |
||||
|
document.body.appendChild(this.container); |
||||
|
} |
||||
|
|
||||
|
this.toasts = []; |
||||
|
this.lastId = 0; |
||||
|
|
||||
|
// Merge user provided global options with defaults
|
||||
|
this.globalOptions = this.extend({}, AbpToastService.defaultOptions, globalOptions); |
||||
|
|
||||
|
this.updateContainerPosition(); |
||||
|
} |
||||
|
|
||||
|
// Deep merge objects
|
||||
|
AbpToastService.prototype.extend = function(target, ...sources) { |
||||
|
sources.forEach(source => { |
||||
|
for (const key in source) { |
||||
|
if (source[key] && typeof source[key] === 'object') { |
||||
|
target[key] = this.extend(target[key] || {}, source[key]); |
||||
|
} else { |
||||
|
target[key] = source[key]; |
||||
|
} |
||||
|
} |
||||
|
}); |
||||
|
return target; |
||||
|
}; |
||||
|
|
||||
|
// Update toast container position based on global options
|
||||
|
AbpToastService.prototype.updateContainerPosition = function() { |
||||
|
const { position } = this.globalOptions; |
||||
|
Object.assign(this.container.style, position); |
||||
|
}; |
||||
|
|
||||
|
// Update global options
|
||||
|
AbpToastService.prototype.setGlobalOptions = function(options) { |
||||
|
this.globalOptions = this.extend({}, this.globalOptions, options); |
||||
|
this.updateContainerPosition(); |
||||
|
}; |
||||
|
|
||||
|
// Get icon class based on severity
|
||||
|
AbpToastService.prototype.getIconClass = function(severity, options) { |
||||
|
// Use custom icon class if provided
|
||||
|
if (options.iconClass) { |
||||
|
return options.iconClass; |
||||
|
} |
||||
|
|
||||
|
const icons = { |
||||
|
success: 'bi-check', |
||||
|
info: 'bi-info-circle', |
||||
|
warning: 'bi-exclamation-triangle', |
||||
|
error: 'bi-shield-exclamation' |
||||
|
}; |
||||
|
return icons[severity] || 'bi-exclamation-triangle'; |
||||
|
}; |
||||
|
|
||||
|
// Create toast DOM element
|
||||
|
AbpToastService.prototype.createToastElement = function(message, title, severity, options) { |
||||
|
const toast = document.createElement('div'); |
||||
|
toast.className = `abp-toast abp-toast-${severity}`; |
||||
|
|
||||
|
const closeButton = options.closable !== false ? |
||||
|
`<button class="abp-toast-close-button">
|
||||
|
<i class="bi bi-x fs-4" aria-hidden="true"></i> |
||||
|
</button>` : ''; |
||||
|
|
||||
|
const titleHtml = title ? `<div class="abp-toast-title">${title}</div>` : ''; |
||||
|
|
||||
|
toast.innerHTML = ` |
||||
|
<div class="abp-toast-icon"> |
||||
|
<i class="bi ${this.getIconClass(severity, options)} icon" aria-hidden="true"></i> |
||||
|
</div> |
||||
|
<div class="abp-toast-content"> |
||||
|
${closeButton} |
||||
|
${titleHtml} |
||||
|
<p class="abp-toast-message">${message}</p> |
||||
|
</div>`; |
||||
|
|
||||
|
// 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 () { |
||||
|
var toast; |
||||
|
|
||||
|
function getToast() { |
||||
|
if (!toast) { |
||||
|
toast = new AbpToastService(); |
||||
|
} |
||||
|
return toast; |
||||
|
} |
||||
|
|
||||
|
abp.notify.success = function (message, title, options) { |
||||
|
getToast().success(message, title, options); |
||||
|
}; |
||||
|
|
||||
|
abp.notify.info = function (message, title, options) { |
||||
|
getToast().info(message, title, options); |
||||
|
}; |
||||
|
|
||||
|
abp.notify.warn = function (message, title, options) { |
||||
|
getToast().warning(message, title, options); |
||||
|
}; |
||||
|
|
||||
|
abp.notify.error = function (message, title, options) { |
||||
|
getToast().error(message, title, options); |
||||
|
}; |
||||
|
})(); |
||||
@ -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); |
|
||||
}; |
|
||||
|
|
||||
})(); |
|
||||
Loading…
Reference in new issue