OpenDTU-old/webapp/src/components/BootstrapAlert.vue
2022-12-25 12:02:10 +01:00

115 lines
3.9 KiB
Vue

<template>
<div v-if="isAlertVisible" ref="element" class="alert" role="alert" :class="classes">
<slot />
<button v-if="dismissible" type="button" class="btn-close" data-bs-dismiss="alert" :aria-label="dismissLabel"
@click="dismissClicked" />
</div>
</template>
<script lang="ts">
import Alert from "bootstrap/js/dist/alert";
import { computed, defineComponent, onBeforeUnmount, ref, watch } from "vue";
export const toInteger = (value: number, defaultValue = NaN) => {
return Number.isInteger(value) ? value : defaultValue;
};
export default defineComponent({
name: "BootstrapAlert",
props: {
dismissLabel: { type: String, default: "Close" },
dismissible: { type: Boolean, default: false },
fade: { type: Boolean, default: false },
modelValue: { type: [Boolean, Number], default: false },
show: { type: Boolean, default: false },
variant: { type: String, default: "info" },
},
emits: ["dismissed", "dismiss-count-down", "update:modelValue"],
setup(props, { emit }) {
const element = ref<HTMLElement>();
const instance = ref<Alert>();
const classes = computed(() => ({
[`alert-${props.variant}`]: props.variant,
show: props.modelValue,
"alert-dismissible": props.dismissible,
fade: props.modelValue,
}));
let _countDownTimeout: number | undefined = 0;
const parseCountDown = (value: boolean | number) => {
if (typeof value === "boolean") {
return 0;
}
const numberValue = toInteger(value, 0);
return numberValue > 0 ? numberValue : 0;
};
const clearCountDownInterval = () => {
if (_countDownTimeout === undefined) return;
clearTimeout(_countDownTimeout);
_countDownTimeout = undefined;
};
const countDown = ref(parseCountDown(props.modelValue));
const isAlertVisible = computed(() => props.modelValue || props.show);
onBeforeUnmount(() => {
clearCountDownInterval();
instance.value?.dispose();
instance.value = undefined;
});
const parsedModelValue = computed(() => {
if (props.modelValue === true) {
return true;
}
if (props.modelValue === false) return false;
if (toInteger(props.modelValue, 0) < 1) {
// Boolean will always return false for the above comparison
return false;
}
return !!props.modelValue;
});
const handleShowAndModelChanged = () => {
countDown.value = parseCountDown(props.modelValue);
if ((parsedModelValue.value || props.show) && !instance.value)
instance.value = new Alert(element.value as HTMLElement);
};
const dismissClicked = () => {
if (typeof props.modelValue === "boolean") {
emit("update:modelValue", false);
} else {
emit("update:modelValue", 0);
}
emit("dismissed");
};
watch(() => props.modelValue, handleShowAndModelChanged);
watch(() => props.show, handleShowAndModelChanged);
watch(countDown, (newValue) => {
clearCountDownInterval();
if (typeof props.modelValue === "boolean") return;
emit("dismiss-count-down", newValue);
if (newValue === 0 && props.modelValue > 0) emit("dismissed");
if (props.modelValue !== newValue) emit("update:modelValue", newValue);
if (newValue > 0) {
_countDownTimeout = setTimeout(() => {
countDown.value--;
}, 1000);
}
});
return {
dismissClicked,
isAlertVisible,
element,
classes,
};
},
});
</script>