Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Notification revamp #468

Merged
merged 22 commits into from
Nov 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
122 changes: 95 additions & 27 deletions assets/style/style.css
Original file line number Diff line number Diff line change
Expand Up @@ -2286,7 +2286,7 @@ a {
position: fixed;
z-index: 10000;
right: 15px;
bottom: 0px;
top: 100px;
display: flex;
flex-direction: column;
}
Expand Down Expand Up @@ -3593,57 +3593,125 @@ select.form-control option {
}
}

.notifyButtonFirst {
border-bottom-right-radius:0px!important;
margin-right:1px!important;
padding: 0px 21px;
}

.notifyButtonSecond {
border-bottom-left-radius:0px!important;
margin-left:1px!important;
width:100%;
text-transform: uppercase;
}

.btn-notification-close {
color: #D5C9EC;
border: 2px solid #8529ED;
border-radius: 0px;
border-bottom-left-radius: 20px;
border-bottom-right-radius: 20px;
margin-left: -1px;
margin-bottom: -1px;
margin-right: -1px;
background: rgb(30,20,49);
background: linear-gradient(0deg, rgba(30,20,49,1) 0%, rgba(44,16,79,1) 100%);
font-weight: 500;
}

.btn-notification-close:hover {
border: 2px solid #8529ED;
color: #c7bbdd;
}

.btn-notification-action {
color: #D5C9EC;
border-radius: 0px;
border-bottom-left-radius: 20px;
border-bottom-right-radius: 20px;
margin-left: -1px;
margin-bottom: -1px;
margin-right: -1px;
background: rgb(30,20,49);
background: linear-gradient(0deg, #741EEA 0%, #9231EE 100%);
font-weight: 500;
}

.btn-notification-action:hover {
color: #c7bbdd;
}

.notifyWrapper .notifyBadgeCount {
position: absolute;
right: 0px;
border: 2px solid #9A21FF;
background-color: #431180;
border-radius: 100px;
width: 24px;
height: 24px;
display: flex;
justify-content: center;
align-items: center;
font-size: 13px;
font-weight: 500;
}

.notifyWrapper {
opacity: 1;
z-index: 999999;
background-color: #320044;
border-radius: 5px;
display: inline-flex;
align-items: stretch;
border: 1px solid #9F00F9;
cursor: pointer;
background-color: #260a47;
border-radius: 11px;
border-bottom-left-radius: 20px;
border-bottom-right-radius: 20px;
border: 1px solid #51199f;
margin-bottom: 15px;
opacity: 0;
transition: all 0.250s ease-in-out;
}

.notifyWrapper .notifyIcon {
padding: 20px 11px;
border-top-left-radius: 5px;
border-bottom-left-radius: 5px;
margin-top: -1px;
margin-left: -1px;
margin-bottom: -1px;
border-top-right-radius: 10px;
border-bottom-right-radius: 10px;
display: flex;
height: 45px;
margin-top: 17px;
margin-left: -5px;
width: 45px;
justify-content: center;
align-items: center;
}

.notifyWrapper .notify-warning {
background-color: #630808;
border-top: 1px solid #FF0000;
border-left: 1px solid #FF0000;
border-bottom: 1px solid #FF0000;
background: rgb(138,0,29);
background: linear-gradient(0deg, rgba(138,0,29,1) 0%, rgba(181,0,0,1) 100%);
border: 1px solid #FF1C50;
}

.notifyWrapper .notify-info {
background-color: #084363;
border-top: 1px solid #0095ff;
border-left: 1px solid #0095ff;
border-bottom: 1px solid #0095ff;
background: rgb(47,18,83);
background: linear-gradient(341deg, rgba(47,18,83,1) 0%, rgba(123,101,157,1) 100%);
border: 1px solid #906DB1;
}

.notifyWrapper .notify-success {
background-color: #1c6308;
border-top: 1px solid #1aff00;
border-left: 1px solid #1aff00;
border-bottom: 1px solid #1aff00;
background: rgb(46,82,0);
background: linear-gradient(341deg, rgba(46,82,0,1) 0%, rgba(95,168,0,1) 100%);
border: 1px solid #69BB00;
}

.notifyWrapper .notifyText {
padding-left: 11px;
padding-right: 17px;
padding-top: 14px;
padding-bottom: 14px;
padding-left: 14px;
padding-right: 40px;
padding-top: 19px;
padding-bottom: 19px;
color: #8c7aa8;
}

.notifyWrapper .notifyText b {
color:#DCD1F3;
}

.sliderStyle .arrow {
Expand Down
35 changes: 31 additions & 4 deletions scripts/alerts/Alert.vue
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import { computed, toRefs } from 'vue';
const props = defineProps({
message: String,
level: String,
notificationCount: Number,
actionName: String,
});

const { message, level } = toRefs(props);
Expand All @@ -25,14 +27,39 @@ const icon = computed(() => {
<template>
<div
class="notifyWrapper"
@click="$emit('click')"
:class="{ [level]: true }"
:style="{ opacity: 1 }"
data-testid="alert"
>
<div class="notifyIcon" :class="{ ['notify-' + level]: true }">
<i class="fas fa-xl" :class="{ [icon]: true }"> </i>
<div class="notifyBadgeCount" v-if="notificationCount > 1">
{{ notificationCount }}
</div>
<div style="display: inline-flex; align-items: stretch">
<div class="notifyIcon" :class="{ ['notify-' + level]: true }">
<i class="fas fa-xl" :class="{ [icon]: true }"> </i>
</div>
<div class="notifyText" v-html="message"></div>
</div>
<div
style="display: flex"
:style="
actionName ? 'flex-direction: row' : 'flex-direction: column'
"
>
<button
:class="actionName ? 'notifyButtonFirst' : ''"
class="btn btn-notification-close"
@click="$emit('hideAlert')"
>
CLOSE
</button>
<button
v-if="actionName"
class="btn btn-notification-action notifyButtonSecond"
@click="$emit('runAction')"
>
{{ actionName }}
</button>
</div>
<div class="notifyText" v-html="message"></div>
</div>
</template>
20 changes: 17 additions & 3 deletions scripts/alerts/Alerts.vue
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,17 @@ watch(alerts, () => {
let count = 1;
const pushAlert = () => {
if (previousAlert) {
const countStr = count === 1 ? '' : ` (x${count})`;
const timeout =
previousAlert.created + previousAlert.timeout - Date.now();
const show = timeout > 0;
if (!show) return;
const alert = ref({
...previousAlert,
message: `${previousAlert.message}${countStr}`,
message: `${previousAlert.message}`,
show,
count,
actionName: previousAlert.actionName,
actionFunc: previousAlert.actionFunc,
// Store original message so we can use it as key.
// This skips the animation in case of multiple errors
original: previousAlert.message,
Expand All @@ -45,6 +47,15 @@ watch(alerts, () => {
pushAlert();
foldedAlerts.value = res;
});

/**
* Run an 'action' connected to an alert
* @param {import('./alert.js').Alert} cAlert - The caller alert which is running an action
*/
function runAction(cAlert) {
cAlert.actionFunc();
cAlert.show = false;
}
</script>

<template>
Expand All @@ -57,7 +68,10 @@ watch(alerts, () => {
<Alert
:message="alert.value.message"
:level="alert.value.level"
@click="alert.value.show = false"
:notificationCount="alert.value.count"
:actionName="alert.value.actionName"
@hideAlert="alert.value.show = false"
@runAction="runAction(alert.value)"
/>
</div>
</transition-group>
Expand Down
41 changes: 36 additions & 5 deletions scripts/alerts/alert.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,30 @@ export class Alert {
*/
created;

constructor({ message, level, timeout = 0, created = Date.now() }) {
/**
* @type{string} The user-readable button title for an Action
*/
actionName;

/**
* @type{function} The function to be executed if the user runs the Action
*/
actionFunc;

constructor({
message,
level,
timeout = 0,
created = Date.now(),
actionName,
actionFunc,
}) {
this.message = message;
this.level = level;
this.timeout = timeout;
this.created = created;
this.actionName = actionName;
this.actionFunc = actionFunc;
}
}

Expand Down Expand Up @@ -55,9 +74,13 @@ export class AlertController {
* @param {'success'|'info'|'warning'} type - The alert level
* @param {string} message - The message to relay to the user
* @param {number?} timeout - The time in `ms` until the alert expires
* @param {string?} actionName - The button title of an optional Action to perform
* @param {function?} actionFunc - The function to execute if the Action button is used
*/
createAlert(level, message, timeout = 10000) {
this.addAlert(new Alert({ level, message, timeout }));
createAlert(level, message, timeout = 10000, actionName, actionFunc) {
this.addAlert(
new Alert({ level, message, timeout, actionName, actionFunc })
);
}

/**
Expand Down Expand Up @@ -94,8 +117,16 @@ export class AlertController {
* @param {'success'|'info'|'warning'} type - The alert level
* @param {string} message - The message to relay to the user
* @param {number?} [timeout] - The time in `ms` until the alert expires
* @param {string?} actionName - The button title of an optional Action to perform
* @param {function?} actionFunc - The function to execute if the Action button is used
*/
export function createAlert(type, message, timeout) {
export function createAlert(type, message, timeout, actionName, actionFunc) {
const alertController = AlertController.getInstance();
return alertController.createAlert(type, message, timeout);
return alertController.createAlert(
type,
message,
timeout,
actionName,
actionFunc
);
}
12 changes: 10 additions & 2 deletions scripts/composables/use_alerts.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,17 @@ export const useAlerts = defineStore('alerts', () => {
* @param {'success'|'info'|'warning'} type - The alert level
* @param {string} message - The message to relay to the user
* @param {number?} timeout - The time in `ms` until the alert expires (Defaults to never expiring)
* @param {string?} actionName - The button title of an optional Action to perform
* @param {function?} actionFunc - The function to execute if the Action button is used
*/
const createAlert = (type, message, timeout) => {
alertController.createAlert(type, message, timeout);
const createAlert = (type, message, timeout, actionName, actionFunc) => {
alertController.createAlert(
type,
message,
timeout,
actionName,
actionFunc
);
};

alertController.subscribe(() => {
Expand Down
14 changes: 12 additions & 2 deletions scripts/global.js
Original file line number Diff line number Diff line change
Expand Up @@ -303,10 +303,20 @@ function subscribeToNetworkEvents() {

getEventEmitter().on('transaction-sent', (success, result) => {
if (success) {
// Prepare an Alert action function to open the TX in the explorer
const network = useNetwork();
const openTxExplorer = () =>
window.open(network.explorerUrl + '/tx/' + result, '_blank');

// Notify the user of their transaction
createAlert(
'success',
`${ALERTS.TX_SENT}<br>${sanitizeHTML(result)}`,
result ? 1250 + result.length * 50 : 3000
`<b>${ALERTS.TX_SENT}</b><br>${sanitizeHTML(
result.substring(0, 24)
)}...`,
15000,
'Open In Explorer',
openTxExplorer
);
} else {
debugError(DebugTopics.NET, 'Error sending transaction:');
Expand Down
17 changes: 14 additions & 3 deletions tests/unit/alert.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -73,9 +73,20 @@ describe('createAlert function', () => {

const message = 'Singleton Alert';
const level = 'info';
createAlert(level, message);

expect(createAlertSpy).toHaveBeenCalledWith(level, message, undefined);
const timeout = 10000;
const actionName = 'Example Calculation';
const actionExampleFunc = () => {
return 5 + 5;
};
createAlert(level, message, timeout, actionName, actionExampleFunc);

expect(createAlertSpy).toHaveBeenCalledWith(
JSKitty marked this conversation as resolved.
Show resolved Hide resolved
level,
message,
timeout,
actionName,
actionExampleFunc
);

createAlertSpy.mockRestore();
});
Expand Down
Loading