Files
MusicPlayer/MusicPlayerV2-GUI/src/components/popupModule.vue
T
2024-06-29 15:48:04 +02:00

304 lines
9.4 KiB
Vue

<template>
<div>
<div :class="'popup-backdrop' + ( isShowingPopup ? '' : ' hidden' )" :style="'transform-origin: ' + transformOriginVertical + ';'">
<div class="popup-main">
<span class="material-symbols-outlined close-icon" @click="closePopup()">close</span>
<h2>{{ popupContent.title }}</h2>
<div v-if="popupContent.popupType === 'information' || popupContent.popupType === 'confirmation'" class="popup-content">
<p v-html="popupContent.subtitle"></p>
</div>
<div v-else class="popup-content">
<p v-if="isShowingIncomplete" class="incomplete-message">{{ popupContent.incompleteMessage ? popupContent.incompleteMessage : 'Some entries are not filled out. Please fill them out to proceed.' }}</p>
<p v-html="popupContent.subtitle"></p>
<div v-for="content in popupContent.data" v-bind:key="content.id" class="popup-content-wrapper">
<div v-if="content.dataType === 'text'">
<label :for="'text-' + content.id">{{ content.name }}</label><br>
<input type="text" :id="'text-' + content.id" v-model="data[ content.id ]" class="input">
</div>
<div v-else-if="content.dataType === 'number'">
<label :for="'number-' + content.id">{{ content.name }}</label><br>
<input type="number" :id="'number-' + content.id" v-model="data[ content.id ]" class="input">
</div>
<div v-else-if="content.dataType === 'checkbox'">
<label :for="'checkbox-' + content.id">{{ content.name }}</label><br>
<label class="switch">
<input type="checkbox" v-model="data[ content.id ]">
<span class="slider round"></span>
</label>
</div>
<div v-else-if="content.dataType === 'textbox'">
<label :for="'textarea-' + content.id">{{ content.name }}</label><br>
<textarea :id="'textarea-' + content.id" v-model="data[ content.id ]" class="textarea"></textarea>
</div>
<div v-else-if="content.dataType === 'colour'">
<label :for="'colour-' + content.id">{{ content.name }}</label><br>
<input type="text" :id="'colour-' + content.id" v-model="data[ content.id ]" class="input">
</div>
</div>
</div>
<div style="margin-top: 20px;">
<button @click="closePopup()" style="margin-right: 10px;" class="fancy-button" v-if="popupContent.popupType === 'confirmation'">Cancel</button>
<button @click="closePopupReturn()" class="fancy-button">Ok</button>
</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
type PopupType = 'confirmation' | 'information' | 'input';
interface PopupData {
/**
* What to display to the user in front of the input field
*/
name: string;
/**
* The type of data to display
*/
dataType: 'text' | 'number' | 'checkbox' | 'textbox' | 'colour';
/**
* ID that is used for internal usage only. May only contain alphanumerical characters, as well as dashes and underscores
*/
id: string;
}
interface PopupContent {
/**
* The title shown in big letters at the top of the popup
*/
title: string;
/**
* (OPTIONAL) The subtitle shown to the user in normal sized letters below title
*/
subtitle?: string;
/**
* (OPTIONAL) The message to show, if the user has not filled out all fields
*/
incompleteMessage?: string;
/**
* The type of popup (i.e. what it is for)
*/
popupType: PopupType;
/**
* (REQUIRED ONLY when popupType === 'input') The input fields to show
*/
data?: PopupData[];
}
interface Data {
[key: string]: any;
}
import "@melloware/coloris/dist/coloris.css";
import { ref, type Ref } from 'vue';
import Coloris from '@melloware/coloris';
Coloris.init();
const isShowingPopup = ref( false );
const transformOriginVertical = ref( '50% 50%' );
const data: Ref<Data> = ref( {} );
const isShowingIncomplete = ref( false );
const popupContent: Ref<PopupContent> = ref( {
title: 'Undefined popup title',
popupType: 'information',
subtitle: 'This popup was not configured correctly during development. Please send a bug-report at <a href="https://support.janishutz.com/index.php?a=add&category=7" target="_blank">support.janishutz.com</a> and inform us about where exactly you encountered this popup! We are sorry for the inconvenience'
} );
const closePopup = () => {
isShowingPopup.value = false;
Coloris.close();
}
const closePopupReturn = () => {
for ( let el in popupContent.value.data ) {
if ( !data.value[ popupContent.value.data[ parseInt( el ) ].id ] && popupContent.value.data[ parseInt( el ) ].dataType !== 'checkbox' ) {
isShowingIncomplete.value = true;
return;
}
}
closePopup();
if ( popupContent.value.popupType === 'confirmation' ) {
emit( 'update', true );
} else {
emit( 'update', data.value );
}
}
const openPopup = ( popupConfig: PopupContent, transformOrigin?: string ) => {
if ( transformOrigin ) {
transformOriginVertical.value = transformOrigin;
} else {
transformOriginVertical.value = '50% 50%';
}
data.value = {};
for ( let el in popupConfig.data ) {
if ( !popupConfig.data[ parseInt( el ) ].id ) {
console.warn( '[ popup ] Missing ID for input with name "' + popupConfig.data[ parseInt( el ) ].name + '"!' );
} else if ( popupConfig.data[ parseInt( el ) ].dataType === 'colour' ) {
Coloris( { el: '#colour-' + popupConfig.data[ parseInt( el ) ].id } );
}
}
isShowingPopup.value = true;
popupContent.value = popupConfig;
for ( const el in popupContent.value.data ) {
if ( popupContent.value.data[ parseInt( el ) ].dataType === 'checkbox' ) {
data.value[ popupContent.value.data[ parseInt( el ) ].id ] = false;
}
}
}
defineExpose( {
openPopup,
} );
const emit = defineEmits( [ 'update' ] );
</script>
<style scoped>
.popup-backdrop {
width: 100vw;
height: 100vh;
position: fixed;
background-color: var( --overlay-color );
top: 0;
left: 0;
display: flex;
justify-content: center;
align-items: center;
overflow: hidden;
transition: all 0.5s;
transform: scale(1);
z-index: 99;
}
.incomplete-message {
color: red;
font-weight: 300;
font-style: italic;
margin-top: 0;
font-size: 0.8rem;
}
.hidden {
transform: scale(0);
}
.popup-main {
width: 40%;
height: 50%;
background-color: var( --secondary-color );
padding: 2.5%;
border-radius: 20px;
position: relative;
overflow-y: scroll;
display: block;
}
.close-icon {
position: absolute;
top: 20px;
right: 20px;
font-size: 2rem;
cursor: pointer;
user-select: none;
}
.popup-content {
position: unset;
height: 60%;
overflow-y: scroll;
}
.textarea {
width: 80%;
resize: vertical;
min-height: 30px;
border-radius: 10px;
border: solid var( --primary-color ) 1px;
padding: 5px;
}
.input {
padding: 5px;
border-radius: 10px;
border: solid var( --primary-color ) 1px;
}
.popup-content-wrapper {
margin-bottom: 10px;
}
</style>
<style scoped>
/* https://www.w3schools.com/howto/tryit.asp?filename=tryhow_css_switch */
.switch {
position: relative;
display: inline-block;
width: 60px;
height: 34px;
}
.switch input {
opacity: 0;
width: 0;
height: 0;
}
.slider {
position: absolute;
cursor: pointer;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: #ccc;
-webkit-transition: .4s;
transition: .4s;
}
.slider:before {
position: absolute;
content: "";
height: 26px;
width: 26px;
left: 4px;
bottom: 4px;
background-color: white;
-webkit-transition: .4s;
transition: .4s;
}
input:checked + .slider {
background-color: #2196F3;
}
input:focus + .slider {
box-shadow: 0 0 1px #2196F3;
}
input:checked + .slider:before {
-webkit-transform: translateX(26px);
-ms-transform: translateX(26px);
transform: translateX(26px);
}
/* Rounded sliders */
.slider.round {
border-radius: 34px;
}
.slider.round:before {
border-radius: 50%;
}
</style>