chg: [front] Added support of toaster
This commit is contained in:
parent
b040f85d59
commit
0370ad08a7
9 changed files with 239 additions and 3 deletions
|
@ -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>
|
||||||
|
|
||||||
|
|
|
@ -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([])
|
||||||
|
|
|
@ -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>
|
||||||
|
|
70
src/components/elements/Alert.vue
Normal file
70
src/components/elements/Alert.vue
Normal 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>
|
97
src/components/elements/Toast.vue
Normal file
97
src/components/elements/Toast.vue
Normal 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>
|
47
src/components/elements/Toaster.vue
Normal file
47
src/components/elements/Toaster.vue
Normal 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>
|
|
@ -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')
|
|
@ -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
|
||||||
|
|
|
@ -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: [
|
||||||
|
|
Loading…
Reference in a new issue