forked from noxious/client
299 lines
6.5 KiB
Vue
299 lines
6.5 KiB
Vue
<template>
|
|
<Teleport to="body">
|
|
<div class="modal-container" v-if="isModalOpenRef" :style="{ top: y + 'px', left: x + 'px', width: width + 'px', height: height + 'px' }">
|
|
<div class="modal-header" @mousedown="startDrag">
|
|
<slot name="modalHeader" />
|
|
<div class="buttons">
|
|
<!-- <button><img alt="resize" draggable="false" src="/assets/icons/modalFullscreen.svg" /></button>-->
|
|
<button @click="close" v-if="closable"><img alt="close" draggable="false" src="/assets/icons/close-button-white.svg" /></button>
|
|
</div>
|
|
</div>
|
|
<div class="modal-body">
|
|
<slot name="modalBody" />
|
|
<img v-if="isResizable" src="/assets/icons/resize-icon.svg" alt="resize" class="resize" @mousedown="startResize" />
|
|
</div>
|
|
<div v-if="$slots.modalFooter" class="modal-footer">
|
|
<slot name="modalFooter" />
|
|
</div>
|
|
</div>
|
|
</Teleport>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import { defineEmits, onMounted, onUnmounted, ref, watch } from 'vue'
|
|
|
|
const properties = defineProps({
|
|
isModalOpen: {
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
closable: {
|
|
type: Boolean,
|
|
default: true
|
|
},
|
|
isResizable: {
|
|
type: Boolean,
|
|
default: true
|
|
},
|
|
modalWidth: {
|
|
type: Number,
|
|
default: 500
|
|
},
|
|
modalHeight: {
|
|
type: Number,
|
|
default: 280
|
|
}
|
|
})
|
|
|
|
watch(() => properties.isModalOpen, (value) => {
|
|
isModalOpenRef.value = value
|
|
}
|
|
)
|
|
|
|
const isModalOpenRef = ref(properties.isModalOpen)
|
|
const emit = defineEmits(['modal:close', 'character:create'])
|
|
|
|
function close() {
|
|
emit('modal:close')
|
|
}
|
|
|
|
// (re)size logics
|
|
const width = ref(properties.modalWidth)
|
|
const height = ref(properties.modalHeight)
|
|
let minWidth = ref(200)
|
|
let minHeight = ref(100)
|
|
let isResizing = ref(false)
|
|
|
|
let startWidth = 0
|
|
let startHeight = 0
|
|
|
|
const startResize = (event: MouseEvent) => {
|
|
isResizing.value = true
|
|
startWidth = width.value - event.clientX
|
|
startHeight = height.value - event.clientY
|
|
event.preventDefault()
|
|
}
|
|
|
|
const resizeModal = (event: MouseEvent) => {
|
|
if (!isResizing.value) return
|
|
let newWidth = startWidth + event.clientX
|
|
let newHeight = startHeight + event.clientY
|
|
width.value = newWidth > minWidth.value ? newWidth : minWidth.value
|
|
height.value = newHeight > minHeight.value ? newHeight : minHeight.value
|
|
}
|
|
|
|
const stopResize = () => {
|
|
isResizing.value = false
|
|
}
|
|
|
|
// make modal draggable
|
|
let startX = 0
|
|
let startY = 0
|
|
let initialX = 0
|
|
let initialY = 0
|
|
const x = ref(0)
|
|
const y = ref(0)
|
|
const isDragging = ref(false)
|
|
|
|
// set modal position center of the screen
|
|
onMounted(() => {
|
|
x.value = window.innerWidth / 2 - 150
|
|
y.value = window.innerHeight / 2 - 100
|
|
})
|
|
|
|
const startDrag = (event: MouseEvent) => {
|
|
isDragging.value = true
|
|
startX = event.clientX
|
|
startY = event.clientY
|
|
initialX = x.value
|
|
initialY = y.value
|
|
event.preventDefault()
|
|
}
|
|
|
|
const drag = (event: MouseEvent) => {
|
|
if (!isDragging.value) return
|
|
const dx = event.clientX - startX
|
|
const dy = event.clientY - startY
|
|
x.value = initialX + dx
|
|
y.value = initialY + dy
|
|
}
|
|
|
|
const stopDrag = () => {
|
|
isDragging.value = false
|
|
}
|
|
|
|
watch(() => properties.modalWidth, (value) => {
|
|
width.value = value
|
|
}
|
|
)
|
|
|
|
watch(() => properties.modalHeight, (value) => {
|
|
height.value = value
|
|
}
|
|
)
|
|
|
|
onMounted(() => {
|
|
addEventListener('mousemove', drag)
|
|
addEventListener('mouseup', stopDrag)
|
|
addEventListener('mousemove', resizeModal)
|
|
addEventListener('mouseup', stopResize)
|
|
addEventListener('resize', handleResize)
|
|
})
|
|
|
|
onUnmounted(() => {
|
|
removeEventListener('mousemove', drag)
|
|
removeEventListener('mouseup', stopDrag)
|
|
removeEventListener('mousemove', resizeModal)
|
|
removeEventListener('mouseup', stopResize)
|
|
removeEventListener('resize', handleResize)
|
|
})
|
|
|
|
|
|
// Make sure modal doesn't go off screen
|
|
watch(() => x.value, (value) => {
|
|
if (value < 0) { x.value = 0 } else if (value + width.value > window.innerWidth) { x.value = window.innerWidth - width.value }
|
|
}
|
|
)
|
|
watch(() => y.value, (value) => {
|
|
if (value < 0) { y.value = 0 } else if (value + height.value > window.innerHeight) { y.value = window.innerHeight - height.value }
|
|
}
|
|
)
|
|
|
|
// also on window resize function
|
|
function handleResize() {
|
|
if (x.value + width.value > window.innerWidth) {
|
|
x.value = window.innerWidth - width.value
|
|
}
|
|
if (y.value + height.value > window.innerHeight) {
|
|
y.value = window.innerHeight - height.value
|
|
}
|
|
}
|
|
</script>
|
|
|
|
<style lang="scss">
|
|
@import '@/assets/scss/main';
|
|
|
|
.modal-container {
|
|
position: fixed;
|
|
top: 0;
|
|
left: 0;
|
|
max-width: 1000px;
|
|
background-color: rgba($dark-gray, 0.8);
|
|
border: 2px solid $dark-cyan;
|
|
z-index: 999;
|
|
display: flex;
|
|
flex-direction: column;
|
|
border-radius: 10px;
|
|
backdrop-filter: blur(5px);
|
|
box-shadow: 0 0 10px rgba($black, 0.5);
|
|
|
|
.modal-header {
|
|
cursor: move;
|
|
padding: 0 20px;
|
|
min-height: 50px;
|
|
display: flex;
|
|
justify-content: space-between;
|
|
gap: 30px;
|
|
align-items: center;
|
|
border-bottom: 1px solid $dark-cyan;
|
|
|
|
.modal-title {
|
|
margin: 0;
|
|
font-weight: 400;
|
|
}
|
|
|
|
.buttons {
|
|
display: flex;
|
|
gap: 10px;
|
|
|
|
button {
|
|
width: 20px;
|
|
height: 20px;
|
|
margin: 0;
|
|
padding: 0;
|
|
position: relative;
|
|
|
|
img {
|
|
width: 100%;
|
|
height: 100%;
|
|
}
|
|
|
|
&:hover {
|
|
transform: rotate(180deg);
|
|
transition: ease-in-out transform 0.3s;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
.modal-body {
|
|
max-height: 80vh;
|
|
overflow: auto;
|
|
padding: 15px;
|
|
flex-grow: 1;
|
|
|
|
.resize {
|
|
filter: invert(60%);
|
|
position: absolute;
|
|
bottom: 0;
|
|
right: 0;
|
|
width: 20px;
|
|
height: 20px;
|
|
cursor: nwse-resize;
|
|
}
|
|
|
|
.submit {
|
|
display: inline-block;
|
|
button {
|
|
margin-right: 20px;
|
|
}
|
|
}
|
|
|
|
button {
|
|
padding: 6px 15px;
|
|
min-width: 100px;
|
|
}
|
|
|
|
.modal-form {
|
|
display: inline;
|
|
|
|
.form-fields {
|
|
display: flex;
|
|
flex-direction: column;
|
|
margin-bottom: 20px;
|
|
|
|
label {
|
|
margin-bottom: 10px;
|
|
font-family: "Poppins";
|
|
}
|
|
|
|
input {
|
|
max-width: 250px;
|
|
font-family: "Poppins";
|
|
border: 1px solid $cyan;
|
|
border-radius: 5px;
|
|
background-color: rgba($white, 0.8);
|
|
padding: 8px 10px;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
.modal-footer {
|
|
padding: 0 20px;
|
|
min-height: 50px;
|
|
display: flex;
|
|
justify-content: end;
|
|
gap: 30px;
|
|
align-items: center;
|
|
border-top: 1px solid $dark-cyan;
|
|
|
|
button {
|
|
padding: 6px 15px;
|
|
min-width: 100px;
|
|
border-radius: 5px;
|
|
border-width: 1px;
|
|
}
|
|
}
|
|
}
|
|
</style> |