xref: /openbmc/webui-vue/src/components/Mixins/BVToastMixin.js (revision d36ac8a8be8636ddd0e64ce005d507b21bcdeb00)
1import { h } from 'vue';
2import StatusIcon from '../Global/StatusIcon';
3import i18n from '@/i18n';
4const BVToastMixin = {
5  components: {
6    StatusIcon,
7  },
8  methods: {
9    $_BVToastMixin_createTitle(title, status) {
10      const statusIcon = h(StatusIcon, { status });
11      return h('strong', { class: 'toast-icon' }, [statusIcon, title]);
12    },
13    $_BVToastMixin_createBody(messageBody) {
14      if (Array.isArray(messageBody)) {
15        return messageBody.map((message) => h('p', { class: 'mb-0' }, message));
16      } else {
17        return [h('p', { class: 'mb-0' }, messageBody)];
18      }
19    },
20    $_BVToastMixin_createTimestamp() {
21      const timestamp = this.$filters.formatTime(new Date());
22      return h('p', { class: 'mt-3 mb-0' }, timestamp);
23    },
24    $_BVToastMixin_createRefreshAction() {
25      return h(
26        'BLink',
27        {
28          class: 'd-inline-block mt-3',
29          onClick: () => {
30            require('@/eventBus').default.$emit('refresh-application');
31          },
32        },
33        i18n.global.t('global.action.refresh'),
34      );
35    },
36    $_BVToastMixin_initToast(body, title, variant) {
37      // Use global toast plugin (works with Options API)
38      // Extract text content from VNodes for display
39
40      // Extract title text from VNode
41      const titleText =
42        typeof title === 'string'
43          ? title
44          : title?.children?.[1] || title?.children || '';
45
46      // Extract body text from VNode array
47      // Each VNode (paragraph) should be on its own line
48      const bodyLines = Array.isArray(body)
49        ? body.map((node) => {
50            if (typeof node === 'string') return node;
51            // Extract text from VNode children
52            const text = node?.children || node?.props?.children || '';
53            // Ensure timestamps and other paragraphs are on separate lines
54            return text;
55          })
56        : [typeof body === 'string' ? body : body?.children || ''];
57
58      // Join with newlines to ensure timestamps appear on their own line
59      const bodyText = bodyLines.filter(Boolean).join('\n');
60
61      // Show toast via global plugin
62      if (this.$toast) {
63        this.$toast.show({
64          body: bodyText,
65          props: {
66            title: titleText,
67            variant,
68            isStatus: true,
69            solid: false, // Use light backgrounds with dark text (not solid colors)
70            // Success toasts auto-dismiss after 10s, others stay until closed
71            interval: variant === 'success' ? 10000 : 0,
72            // Note: Progress bar hidden via CSS in _toasts.scss (JS props to hide progress bar don't work as documented in Bootstrap Vue Next 0.40.8)
73          },
74        });
75      } else {
76        // Fallback: log to console
77        /* eslint-disable no-console */
78        console[variant === 'danger' ? 'error' : 'log'](
79          `[toast:${variant}]`,
80          bodyText,
81        );
82        /* eslint-enable no-console */
83      }
84    },
85    successToast(
86      message,
87      {
88        title: t = i18n.global.t('global.status.success'),
89        timestamp,
90        refreshAction,
91      } = {},
92    ) {
93      const body = this.$_BVToastMixin_createBody(message);
94      const title = this.$_BVToastMixin_createTitle(t, 'success');
95      if (refreshAction) body.push(this.$_BVToastMixin_createRefreshAction());
96      if (timestamp) {
97        body.push(' '); // Extra newline for spacing above timestamp
98        body.push(this.$_BVToastMixin_createTimestamp());
99      }
100      this.$_BVToastMixin_initToast(body, title, 'success');
101    },
102    errorToast(
103      message,
104      {
105        title: t = i18n.global.t('global.status.error'),
106        timestamp,
107        refreshAction,
108      } = {},
109    ) {
110      const body = this.$_BVToastMixin_createBody(message);
111      const title = this.$_BVToastMixin_createTitle(t, 'danger');
112      if (refreshAction) body.push(this.$_BVToastMixin_createRefreshAction());
113      if (timestamp) {
114        body.push(' '); // Extra newline for spacing above timestamp
115        body.push(this.$_BVToastMixin_createTimestamp());
116      }
117      this.$_BVToastMixin_initToast(body, title, 'danger');
118    },
119    warningToast(
120      message,
121      {
122        title: t = i18n.global.t('global.status.warning'),
123        timestamp,
124        refreshAction,
125      } = {},
126    ) {
127      const body = this.$_BVToastMixin_createBody(message);
128      const title = this.$_BVToastMixin_createTitle(t, 'warning');
129      if (refreshAction) body.push(this.$_BVToastMixin_createRefreshAction());
130      if (timestamp) {
131        body.push(' '); // Extra newline for spacing above timestamp
132        body.push(this.$_BVToastMixin_createTimestamp());
133      }
134      this.$_BVToastMixin_initToast(body, title, 'warning');
135    },
136    infoToast(
137      message,
138      {
139        title: t = i18n.global.t('global.status.informational'),
140        timestamp,
141        refreshAction,
142      } = {},
143    ) {
144      const body = this.$_BVToastMixin_createBody(message);
145      const title = this.$_BVToastMixin_createTitle(t, 'info');
146      if (refreshAction) body.push(this.$_BVToastMixin_createRefreshAction());
147      if (timestamp) {
148        body.push(' '); // Extra newline for spacing above timestamp
149        body.push(this.$_BVToastMixin_createTimestamp());
150      }
151      this.$_BVToastMixin_initToast(body, title, 'info');
152    },
153  },
154};
155
156export default BVToastMixin;
157