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