From 894fd5efd396d8e3cc395495f20afaaa10947453 Mon Sep 17 00:00:00 2001 From: Arturo Manzoli Date: Mon, 30 Sep 2024 10:56:06 -0300 Subject: [PATCH] Components: Tutorial: Make tutorial modal draggable Signed-off-by: Arturo Manzoli --- src/components/GlassModal.vue | 97 ++++++++++++++++++++++++++++++++++- src/components/Tutorial.vue | 15 ++++-- 2 files changed, 107 insertions(+), 5 deletions(-) diff --git a/src/components/GlassModal.vue b/src/components/GlassModal.vue index dd6b93f9b..3c403b3be 100644 --- a/src/components/GlassModal.vue +++ b/src/components/GlassModal.vue @@ -3,9 +3,14 @@ v-if="isVisible" ref="modal" class="glass-modal" - :class="[interfaceStore.isOnSmallScreen ? 'rounded-[10px]' : 'rounded-[20px]', selectedOverflow]" + :class="[ + interfaceStore.isOnSmallScreen ? 'rounded-[10px]' : 'rounded-[20px]', + selectedOverflow, + { 'cursor-move': isDraggable }, + ]" :style="[modalPositionStyle, interfaceStore.globalGlassMenuStyles]" @click="bringModalUp" + @mousedown="startDragging" > @@ -33,15 +38,26 @@ const props = defineProps<{ */ position?: ModalPosition /** - * + * If true, modal will not close by pressing 'esc' or by an outside click. */ isPersistent?: boolean /** * The overflow property of the modal. */ overflow?: 'auto' | 'hidden' | 'scroll' | 'visible' | 'inherit' | 'initial' | 'unset' + /** + * If true, the modal can be dragged around. + */ + draggable?: boolean + /** + * The storage key to save the modal position in localStorage. + */ + storageKey?: string }>() +// eslint-disable-next-line +type ModalDraggedPosition = { left: number; top: number } + const emit = defineEmits(['outside-click']) const isVisible = ref(props.isVisible) @@ -51,6 +67,51 @@ const isPersistent = ref(props.isPersistent || false) const modal = ref(null) const zIndexToggle = ref(200) const isAlwaysOnTop = ref(false) +const isDraggable = ref(props.draggable || false) +const isDragging = ref(false) +const dragStartX = ref(0) +const dragStartY = ref(0) +const modalStartX = ref(0) +const modalStartY = ref(0) +const customPosition = ref(null) + +const startDragging = (e: MouseEvent): void => { + if (!isDraggable.value) return + isDragging.value = true + dragStartX.value = e.clientX + dragStartY.value = e.clientY + + const modalElement = modal.value + if (modalElement) { + const rect = modalElement.getBoundingClientRect() + modalStartX.value = rect.left + modalStartY.value = rect.top + } + + document.addEventListener('mousemove', onMouseMove) + document.addEventListener('mouseup', onMouseUp) +} + +const onMouseMove = (e: MouseEvent): void => { + if (!isDragging.value) return + + const deltaX = e.clientX - dragStartX.value + const deltaY = e.clientY - dragStartY.value + const newLeft = modalStartX.value + deltaX + const newTop = modalStartY.value + deltaY + + customPosition.value = { left: newLeft, top: newTop } + + if (props.storageKey) { + localStorage.setItem(props.storageKey, JSON.stringify({ left: newLeft, top: newTop })) + } +} + +const onMouseUp = (): void => { + isDragging.value = false + document.removeEventListener('mousemove', onMouseMove) + document.removeEventListener('mouseup', onMouseUp) +} const bringModalUp = (): void => { zIndexToggle.value = 2000 @@ -70,12 +131,22 @@ watch( ) const modalPositionStyle = computed(() => { + if (customPosition.value) { + return { + top: `${customPosition.value.top}px`, + left: `${customPosition.value.left}px`, + transform: 'none', + zIndex: zIndexToggle.value, + } + } + switch (modalPosition.value) { case 'left': return { top: '50%', left: '0%', transform: 'translateY(-50%)', + zIndex: zIndexToggle.value, } case 'menuitem': return { @@ -84,6 +155,7 @@ const modalPositionStyle = computed(() => { ? `${interfaceStore.mainMenuWidth - 20}px` : `${interfaceStore.mainMenuWidth + 30}px`, transform: 'translateY(-50%)', + zIndex: zIndexToggle.value, } case 'center': default: @@ -97,6 +169,10 @@ const modalPositionStyle = computed(() => { }) const closeModal = (): void => { + if (props.storageKey) { + localStorage.removeItem(props.storageKey) + } + customPosition.value = null emit('outside-click') } @@ -109,6 +185,23 @@ onClickOutside(modal, () => { } }) +watch(isVisible, (newVal) => { + if (newVal) { + if (props.storageKey) { + const savedPosition = localStorage.getItem(props.storageKey) + if (savedPosition) { + const position = JSON.parse(savedPosition) + customPosition.value = position + } else { + customPosition.value = null + } + } + } + if (!newVal) { + closeModal() + } +}) + watch( () => props.isVisible, (newVal) => { diff --git a/src/components/Tutorial.vue b/src/components/Tutorial.vue index eff7d908f..ea0dc7312 100644 --- a/src/components/Tutorial.vue +++ b/src/components/Tutorial.vue @@ -1,7 +1,13 @@