chg: [front] Added support of toaster

This commit is contained in:
Sami Mokaddem 2024-07-25 11:23:17 +02:00
parent b040f85d59
commit 0370ad08a7
9 changed files with 239 additions and 3 deletions

View file

@ -4,6 +4,7 @@ import TheThemeButton from './components/TheThemeButton.vue'
import TheAdminPanel from './components/TheAdminPanel.vue' import TheAdminPanel from './components/TheAdminPanel.vue'
import TheSocketConnectionState from './components/TheSocketConnectionState.vue' import TheSocketConnectionState from './components/TheSocketConnectionState.vue'
import TheDahboard from './TheDahboard.vue' import TheDahboard from './TheDahboard.vue'
import Toaster from '@/components/elements/Toaster.vue'
import { socketConnected } from "@/socket"; import { socketConnected } from "@/socket";
import { darkModeEnabled } from "@/settings.js" import { darkModeEnabled } from "@/settings.js"
@ -34,6 +35,7 @@ onMounted(() => {
<div class="mt-12"> <div class="mt-12">
<TheDahboard></TheDahboard> <TheDahboard></TheDahboard>
</div> </div>
<Toaster></Toaster>
</main> </main>
</template> </template>

View file

@ -3,6 +3,7 @@
import { exercises, selected_exercises, diagnostic, fullReload, resetAllExerciseProgress, resetAll, resetLiveLogs, changeExerciseSelection, debouncedGetDiangostic, remediateSetting } from "@/socket"; import { exercises, selected_exercises, diagnostic, fullReload, resetAllExerciseProgress, resetAll, resetLiveLogs, changeExerciseSelection, debouncedGetDiangostic, remediateSetting } from "@/socket";
import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome' import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome'
import { faScrewdriverWrench, faTrash, faSuitcaseMedical, faGraduationCap, faBan, faRotate, faHammer, faCheck } from '@fortawesome/free-solid-svg-icons' import { faScrewdriverWrench, faTrash, faSuitcaseMedical, faGraduationCap, faBan, faRotate, faHammer, faCheck } from '@fortawesome/free-solid-svg-icons'
import { toast } from '@/utils.js'
const admin_modal = ref(null) const admin_modal = ref(null)
const clickedButtons = ref([]) const clickedButtons = ref([])

View file

@ -64,7 +64,7 @@
<template> <template>
<div class="my-2 --ml-1 bg-slate-50 dark:bg-slate-600 py-1 pl-1 pr-3 rounded-md relative flex flex-col"> <div class="my-2 --ml-1 bg-slate-50 dark:bg-slate-600 py-1 pl-1 pr-3 rounded-md relative flex flex-col">
<div :class="`${!hasActivity ? 'hidden' : 'absolute'} h-10 -mt-1 w-full z-30`"> <div :class="`${!hasActivity ? 'hidden' : 'absolute'} h-10 -mt-1 w-full z-30`">
<div class="text-xxs flex justify-between h-full items-center text-slate-500 dark:text-slate-300 select-none"> <div class="text-2xs flex justify-between h-full items-center text-slate-500 dark:text-slate-300 select-none">
<span class="-rotate-90 w-8 -ml-3">- {{ notificationHistoryConfig.buffer_timestamp_min }}min</span> <span class="-rotate-90 w-8 -ml-3">- {{ notificationHistoryConfig.buffer_timestamp_min }}min</span>
<span class="-rotate-90 w-8 text-xs"></span> <span class="-rotate-90 w-8 text-xs"></span>
<span class="-rotate-90 w-8 text-lg"></span> <span class="-rotate-90 w-8 text-lg"></span>

View file

@ -0,0 +1,70 @@
<script setup>
import {
faCircleCheck,
faCircleExclamation,
faCircleInfo,
faTriangleExclamation
} from '@fortawesome/free-solid-svg-icons'
import { computed } from 'vue'
const props = defineProps({
title: String,
message: [Array, String],
variant: {
type: String,
validator(value, props) {
return ['success', 'warning', 'danger', 'info'].includes(value)
},
default: 'info'
}
})
const icon = computed(() => {
if (props.variant == 'success') {
return faCircleCheck
} else if (props.variant == 'warning') {
return faCircleExclamation
} else if (props.variant == 'danger') {
return faTriangleExclamation
}
return faCircleInfo
})
const variantClass = computed(() => {
if (props.variant == 'success') {
return 'green'
} else if (props.variant == 'warning') {
return 'amber'
} else if (props.variant == 'danger') {
return 'red'
}
return 'blue'
})
const messages = computed(() => {
return Array.isArray(props.message)
? props.message
: props.message !== undefined
? [props.message]
: []
})
</script>
<template>
<div
:class="`mb-2 p-2 border-l-4 text-left rounded dark:bg-${variantClass}-300 dark:text-slate-900 dark:border-${variantClass}-700 bg-${variantClass}-200 text-slate-900 border-${variantClass}-700`"
>
<FontAwesomeIcon
:icon="icon"
:class="`text-${variantClass}-700 text-lg mx-3`"
></FontAwesomeIcon>
<strong class="mr-2">{{ props.title }}</strong>
<p
class="text-slate-700 p-1 font-light whitespace-pre"
v-for="(message, i) in messages"
:key="i"
>
{{ message }}
</p>
</div>
</template>

View file

@ -0,0 +1,97 @@
<script setup>
import { computed, onMounted, ref, watch } from 'vue'
import { removeToast } from '@/utils.js'
import {
faCancel,
faCheck,
faCircleCheck,
faCircleExclamation,
faCircleInfo,
faClose,
faTriangleExclamation
} from '@fortawesome/free-solid-svg-icons'
const emit = defineEmits(['close'])
const props = defineProps({
id: Number,
title: String,
message: String,
variant: {
type: String,
validator(value, props) {
return ['success', 'warning', 'danger', 'info'].includes(value)
},
default: 'info'
},
confirm: Boolean,
confirmCb: Function
})
const duration = 7000
const icon = computed(() => {
if (props.variant == 'success') {
return faCircleCheck
} else if (props.variant == 'warning') {
return faCircleExclamation
} else if (props.variant == 'danger') {
return faTriangleExclamation
}
return faCircleInfo
})
const variantClass = computed(() => {
if (props.variant == 'success') {
return 'green'
} else if (props.variant == 'warning') {
return 'amber'
} else if (props.variant == 'danger') {
return 'red'
}
return 'blue'
})
function close() {
emit('close')
}
function callConfirmCb() {
props.confirmCb()
close()
}
onMounted(() => {
setTimeout(() => {
removeToast(props.id)
}, duration)
})
</script>
<template>
<div :class="`flex flex-col min-w-72 py-2 px-3 rounded bg-${variantClass}-100`">
<div :class="`text-${variantClass}-800 flex items-center`">
<FontAwesomeIcon :icon="icon" class="mr-2"></FontAwesomeIcon>
<span class="font-semibold">{{ props.title }}</span>
<button class="ml-auto w-6 btn btn-link !p-0">
<FontAwesomeIcon
:icon="faClose"
class="text-gray-500 hover:text-gray-700"
fixed-width
@click="close()"
></FontAwesomeIcon>
</button>
</div>
<div class="text-slate-600 p-1 font-light" v-if="props.message !== undefined && props.message.length > 0">
{{ props.message }}
</div>
<div v-if="props.confirm" class="flex gap-1">
<button class="btn btn-info" @click="callConfirmCb()">
<FontAwesomeIcon :icon="faCheck" class="fa-fw"></FontAwesomeIcon> Confirm
</button>
<button class="btn" @click="close()">
<FontAwesomeIcon :icon="faCancel" class="fa-fw"></FontAwesomeIcon> Cancel
</button>
</div>
</div>
</template>

View file

@ -0,0 +1,47 @@
<script setup>
import { computed, ref, watch } from 'vue'
import { allToasts, removeToast } from '@/utils.js'
import Toast from '@/components/elements/Toast.vue'
const container = ref()
const toasts = computed(() => allToasts.value)
function remove(toast_id) {
removeToast(toast_id)
}
</script>
<template>
<Teleport to="body">
<div ref="container" class="fixed m-3 top-0 right-0">
<transition-group name="list" tag="div" class="flex flex-col-reverse gap-2">
<Toast
v-for="toast of toasts"
:key="toast.id"
:id="toast.id"
:title="toast.title"
:message="toast.message"
:variant="toast.variant"
:confirm="toast.confirm"
:confirmCb="toast.confirmCb"
@close="remove(toast.id)"
/>
</transition-group>
</div>
</Teleport>
</template>
<style>
.list-enter-active,
.list-leave-active {
transition: all 0.3s ease-out;
}
.list-enter-from {
opacity: 0;
transform: translateY(20px);
}
.list-leave-to {
opacity: 0;
transform: translateX(10px);
}
</style>

View file

@ -1,17 +1,19 @@
import './assets/main.css' import './assets/main.css'
import VueApexCharts from "vue3-apexcharts"; import VueApexCharts from "vue3-apexcharts";
import { createApp } from 'vue' import { createApp, ref } from 'vue'
import App from './App.vue' import App from './App.vue'
import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome' import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome'
import Modal from "@/components/elements/Modal.vue" import Modal from "@/components/elements/Modal.vue"
import Loading from "@/components/elements/Loading.vue" import Loading from "@/components/elements/Loading.vue"
import Alert from "@/components/elements/Alert.vue"
const app = createApp(App) const app = createApp(App)
app.component('FontAwesomeIcon', FontAwesomeIcon) app.component('FontAwesomeIcon', FontAwesomeIcon)
app.component('Modal', Modal) app.component('Modal', Modal)
app.component('Loading', Loading) app.component('Loading', Loading)
app.component('Alert', Alert)
app.use(VueApexCharts) app.use(VueApexCharts)
app.mount('#app') app.mount('#app')

View file

@ -1,5 +1,8 @@
import { computed, ref } from "vue"
let toastID = 0 let toastID = 0
export const toastBuffer = ref([]) export const toastBuffer = ref([])
export const allToasts = computed(() => toastBuffer.value)
export function toast(toast) { export function toast(toast) {
toastID += 1 toastID += 1
toast.id = toastID toast.id = toastID

View file

@ -8,6 +8,15 @@ export default {
{ {
pattern: /bg-blue+/, // Includes bg of all colors and shades pattern: /bg-blue+/, // Includes bg of all colors and shades
}, },
{
pattern: /bg-(red|green|blue|amber)-(100|200|300|800)/, // Includes bg of all colors and shades
},
{
pattern: /text-(red|green|blue|amber)-(100|700|800)/, // Includes bg of all colors and shades
},
{
pattern: /border-(red|green|blue|amber)-(700)/, // Includes bg of all colors and shades
},
], ],
theme: { theme: {
extend: { extend: {
@ -18,8 +27,13 @@ export default {
'3xl': '1800px', '3xl': '1800px',
}, },
fontSize: { fontSize: {
'xxs': '0.6rem', '2xs': '0.66rem',
}, },
maxWidth: {
'8xl': '88rem',
'9xl': '96rem',
'10xl': '104rem',
}
}, },
}, },
plugins: [ plugins: [