[FEAT] Streamline UI

This commit is contained in:
2026-01-01 19:47:07 +01:00
parent 04f88dd878
commit e51cb4b334
9 changed files with 292 additions and 293 deletions

View File

@@ -85,5 +85,3 @@
} }
}; };
</script> </script>

View File

@@ -33,32 +33,7 @@
</div> </div>
<!-- Copy to Frame Modal --> <!-- Copy to Frame Modal -->
<div v-if="showCopyToFrameModal" class="fixed inset-0 bg-black/50 z-[60] flex items-center justify-center" @click.self="closeCopyToFrameModal"> <CopyToFrameModal :is-open="showCopyToFrameModal" :layers="props.layers" :initial-layer-id="copyTargetLayerId" @close="closeCopyToFrameModal" @copy="confirmCopyToFrame" />
<div class="bg-white dark:bg-gray-800 rounded-xl shadow-2xl p-6 min-w-[320px] max-w-md" @click.stop>
<h3 class="text-lg font-bold text-gray-900 dark:text-gray-100 mb-4">Copy sprite to frame</h3>
<div class="space-y-4">
<div>
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">Target frame</label>
<select v-model.number="copyTargetFrame" class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100 focus:ring-2 focus:ring-blue-500">
<option v-for="i in maxFrameCount" :key="i" :value="i - 1">Frame {{ i }}</option>
</select>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">Target layer</label>
<select v-model="copyTargetLayerId" class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100 focus:ring-2 focus:ring-blue-500">
<option v-for="layer in props.layers" :key="layer.id" :value="layer.id">{{ layer.name }}</option>
</select>
</div>
</div>
<div class="flex justify-end gap-3 mt-6">
<button @click="closeCopyToFrameModal" class="px-4 py-2 text-gray-600 dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-700 rounded-lg transition-colors">Cancel</button>
<button @click="confirmCopyToFrame" class="px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors">Copy</button>
</div>
</div>
</div>
</Teleport> </Teleport>
<div class="h-full w-full flex flex-col p-4"> <div class="h-full w-full flex flex-col p-4">
@@ -206,6 +181,7 @@
import { useFileDrop } from '@/composables/useFileDrop'; import { useFileDrop } from '@/composables/useFileDrop';
import { useGridMetrics } from '@/composables/useGridMetrics'; import { useGridMetrics } from '@/composables/useGridMetrics';
import { useBackgroundStyles } from '@/composables/useBackgroundStyles'; import { useBackgroundStyles } from '@/composables/useBackgroundStyles';
import CopyToFrameModal from './utilities/CopyToFrameModal.vue';
import type { Layer } from '@/types/sprites'; import type { Layer } from '@/types/sprites';
@@ -302,15 +278,9 @@
// Copy to frame modal state // Copy to frame modal state
const showCopyToFrameModal = ref(false); const showCopyToFrameModal = ref(false);
const copyTargetFrame = ref(0);
const copyTargetLayerId = ref(props.activeLayerId); const copyTargetLayerId = ref(props.activeLayerId);
const copySpriteId = ref<string | null>(null); const copySpriteId = ref<string | null>(null);
const maxFrameCount = computed(() => {
const maxLen = Math.max(1, ...props.layers.map(l => l.sprites.length));
return maxLen + 1; // Allow copying to one frame beyond current max
});
// Clear selection when toggling multi-select mode // Clear selection when toggling multi-select mode
watch( watch(
() => props.isMultiSelectMode, () => props.isMultiSelectMode,
@@ -556,7 +526,6 @@
if (contextMenuSpriteId.value) { if (contextMenuSpriteId.value) {
copySpriteId.value = contextMenuSpriteId.value; copySpriteId.value = contextMenuSpriteId.value;
copyTargetLayerId.value = props.activeLayerId; copyTargetLayerId.value = props.activeLayerId;
copyTargetFrame.value = 0;
showCopyToFrameModal.value = true; showCopyToFrameModal.value = true;
showContextMenu.value = false; showContextMenu.value = false;
} }
@@ -567,9 +536,9 @@
copySpriteId.value = null; copySpriteId.value = null;
}; };
const confirmCopyToFrame = () => { const confirmCopyToFrame = (targetLayerId: string, targetFrameIndex: number) => {
if (copySpriteId.value) { if (copySpriteId.value) {
emit('copySpriteToFrame', copySpriteId.value, copyTargetLayerId.value, copyTargetFrame.value); emit('copySpriteToFrame', copySpriteId.value, targetLayerId, targetFrameIndex);
closeCopyToFrameModal(); closeCopyToFrameModal();
} }
}; };

View File

@@ -24,32 +24,7 @@
</div> </div>
<!-- Copy to Frame Modal --> <!-- Copy to Frame Modal -->
<div v-if="showCopyToFrameModal" class="fixed inset-0 bg-black/50 z-[60] flex items-center justify-center" @click.self="closeCopyToFrameModal"> <CopyToFrameModal :is-open="showCopyToFrameModal" :layers="props.layers" :initial-layer-id="copyTargetLayerId" @close="closeCopyToFrameModal" @copy="confirmCopyToFrame" />
<div class="bg-white dark:bg-gray-800 rounded-xl shadow-2xl p-6 min-w-[320px] max-w-md" @click.stop>
<h3 class="text-lg font-bold text-gray-900 dark:text-gray-100 mb-4">Copy Sprite to Frame</h3>
<div class="space-y-4">
<div>
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">Target Frame</label>
<select v-model.number="copyTargetFrame" class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100 focus:ring-2 focus:ring-blue-500">
<option v-for="i in maxFrameCount" :key="i" :value="i - 1">Frame {{ i }}</option>
</select>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">Target Layer</label>
<select v-model="copyTargetLayerId" class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100 focus:ring-2 focus:ring-blue-500">
<option v-for="layer in props.layers" :key="layer.id" :value="layer.id">{{ layer.name }}</option>
</select>
</div>
</div>
<div class="flex justify-end gap-3 mt-6">
<button @click="closeCopyToFrameModal" class="px-4 py-2 text-gray-600 dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-700 rounded-lg transition-colors">Cancel</button>
<button @click="confirmCopyToFrame" class="px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors">Copy</button>
</div>
</div>
</div>
</Teleport> </Teleport>
<div class="spritesheet-preview w-full h-full" @click="hideContextMenu"> <div class="spritesheet-preview w-full h-full" @click="hideContextMenu">
@@ -308,6 +283,7 @@
import { useAnimationFrames } from '@/composables/useAnimationFrames'; import { useAnimationFrames } from '@/composables/useAnimationFrames';
import { useGridMetrics } from '@/composables/useGridMetrics'; import { useGridMetrics } from '@/composables/useGridMetrics';
import { useBackgroundStyles } from '@/composables/useBackgroundStyles'; import { useBackgroundStyles } from '@/composables/useBackgroundStyles';
import CopyToFrameModal from './utilities/CopyToFrameModal.vue';
const props = defineProps<{ const props = defineProps<{
layers: Layer[]; layers: Layer[];
@@ -373,14 +349,8 @@
// Copy to frame modal state // Copy to frame modal state
const showCopyToFrameModal = ref(false); const showCopyToFrameModal = ref(false);
const copyTargetFrame = ref(0);
const copyTargetLayerId = ref(props.activeLayerId); const copyTargetLayerId = ref(props.activeLayerId);
const maxFrameCount = computed(() => {
const maxLen = Math.max(1, ...props.layers.map(l => l.sprites.length));
return maxLen + 1; // Allow copying to one frame beyond current max
});
// Drag and drop for new sprites // Drag and drop for new sprites
const onDragOver = () => { const onDragOver = () => {
isDragOver.value = true; isDragOver.value = true;
@@ -713,7 +683,6 @@
const openCopyToFrameModal = () => { const openCopyToFrameModal = () => {
if (contextMenuSpriteId.value) { if (contextMenuSpriteId.value) {
copyTargetLayerId.value = contextMenuLayerId.value || props.activeLayerId; copyTargetLayerId.value = contextMenuLayerId.value || props.activeLayerId;
copyTargetFrame.value = 0;
showCopyToFrameModal.value = true; showCopyToFrameModal.value = true;
showContextMenu.value = false; showContextMenu.value = false;
} }
@@ -723,9 +692,9 @@
showCopyToFrameModal.value = false; showCopyToFrameModal.value = false;
}; };
const confirmCopyToFrame = () => { const confirmCopyToFrame = (targetLayerId: string, targetFrameIndex: number) => {
if (contextMenuSpriteId.value) { if (contextMenuSpriteId.value) {
emit('copySpriteToFrame', contextMenuSpriteId.value, copyTargetLayerId.value, copyTargetFrame.value); emit('copySpriteToFrame', contextMenuSpriteId.value, targetLayerId, targetFrameIndex);
closeCopyToFrameModal(); closeCopyToFrameModal();
contextMenuSpriteId.value = null; contextMenuSpriteId.value = null;
} }

View File

@@ -1,13 +1,7 @@
<template> <template>
<Teleport to="body"> <Modal :is-open="isOpen" @close="close" :title="isLogin ? 'Welcome Back' : 'Create Account'" :initialWidth="450">
<div v-if="isOpen" class="fixed inset-0 z-50 flex items-center justify-center bg-black/50 backdrop-blur-sm" @click.self="close"> <div class="space-y-4">
<div class="bg-white dark:bg-gray-800 rounded-xl shadow-2xl w-full max-w-md p-6 relative border border-gray-200 dark:border-gray-700">
<button @click="close" class="absolute top-4 right-4 text-gray-400 hover:text-gray-600 dark:hover:text-gray-200">
<i class="fas fa-times"></i>
</button>
<div class="text-center mb-6"> <div class="text-center mb-6">
<h2 class="text-2xl font-bold text-gray-900 dark:text-gray-100">{{ isLogin ? 'Welcome Back' : 'Create Account' }}</h2>
<p class="text-sm text-gray-500 dark:text-gray-400"> <p class="text-sm text-gray-500 dark:text-gray-400">
{{ isLogin ? 'Sign in to access your projects' : 'Join to save and manage your spritesheets' }} {{ isLogin ? 'Sign in to access your projects' : 'Join to save and manage your spritesheets' }}
</p> </p>
@@ -46,13 +40,13 @@
</button> </button>
</div> </div>
</div> </div>
</div> </Modal>
</Teleport>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { ref } from 'vue'; import { ref } from 'vue';
import { useAuthStore } from '@/stores/useAuthStore'; import { useAuthStore } from '@/stores/useAuthStore';
import Modal from '@/components/utilities/Modal.vue';
const props = defineProps<{ const props = defineProps<{
isOpen: boolean; isOpen: boolean;

View File

@@ -1,8 +1,6 @@
<template> <template>
<Teleport to="body"> <Modal :is-open="isOpen" @close="close" title="New project" :initialWidth="400">
<div v-if="isOpen" class="fixed inset-0 z-50 flex items-center justify-center bg-black/50 backdrop-blur-sm" @click.self="close"> <div class="px-2 pt-2 pb-1">
<div class="bg-white dark:bg-gray-800 rounded-xl shadow-xl p-6 w-full max-w-sm border border-gray-200 dark:border-gray-700">
<h2 class="text-xl font-bold mb-4 text-gray-900 dark:text-gray-100">New project</h2>
<form @submit.prevent="handleSubmit"> <form @submit.prevent="handleSubmit">
<div class="grid grid-cols-2 gap-4 mb-4"> <div class="grid grid-cols-2 gap-4 mb-4">
<div> <div>
@@ -38,12 +36,12 @@
</div> </div>
</form> </form>
</div> </div>
</div> </Modal>
</Teleport>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { ref, computed, watch } from 'vue'; import { ref, computed, watch } from 'vue';
import Modal from '@/components/utilities/Modal.vue';
const props = defineProps<{ const props = defineProps<{
isOpen: boolean; isOpen: boolean;

View File

@@ -1,20 +1,12 @@
<template> <template>
<Teleport to="body"> <Modal :is-open="isOpen" @close="close" title="My Projects" :initialWidth="900" :initialHeight="800" noPadding>
<div v-if="isOpen" class="fixed inset-0 z-50 flex items-center justify-center bg-black/60 backdrop-blur-sm p-4" @click.self="close"> <div class="flex flex-col h-full bg-white dark:bg-gray-900">
<div class="bg-white dark:bg-gray-900 rounded-2xl shadow-2xl w-full max-w-4xl overflow-hidden border border-gray-200 dark:border-gray-800 flex flex-col h-[85vh] animate-fade-in-up"> <!-- Search Header -->
<!-- Header --> <div class="px-6 py-4 border-b border-gray-100 dark:border-gray-800 bg-gray-50/50 dark:bg-gray-800/20">
<div class="px-6 py-5 border-b border-gray-100 dark:border-gray-800 flex flex-col gap-4 bg-gray-50/50 dark:bg-gray-800/20"> <div class="mb-4">
<div class="flex items-center justify-between">
<div>
<h2 class="text-xl font-bold text-gray-900 dark:text-gray-100">My projects</h2>
<p class="text-sm text-gray-500 dark:text-gray-400">Manage your saved sprite sheets</p> <p class="text-sm text-gray-500 dark:text-gray-400">Manage your saved sprite sheets</p>
</div> </div>
<button @click="close" class="w-8 h-8 rounded-full flex items-center justify-center text-gray-400 hover:text-gray-600 dark:hover:text-gray-200 hover:bg-gray-100 dark:hover:bg-gray-800 transition-colors">
<i class="fas fa-times"></i>
</button>
</div>
<!-- Search Bar -->
<div class="relative"> <div class="relative">
<i class="fas fa-search absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400"></i> <i class="fas fa-search absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400"></i>
<input <input
@@ -121,14 +113,14 @@
</div> </div>
</div> </div>
</div> </div>
</div> </Modal>
</Teleport>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { onMounted, toRefs, watch, ref, computed } from 'vue'; import { onMounted, toRefs, watch, ref, computed } from 'vue';
import { useProjectStore, type Project } from '@/stores/useProjectStore'; import { useProjectStore, type Project } from '@/stores/useProjectStore';
import { useProjectManager } from '@/composables/useProjectManager'; import { useProjectManager } from '@/composables/useProjectManager';
import Modal from '@/components/utilities/Modal.vue';
const props = defineProps<{ const props = defineProps<{
isOpen: boolean; isOpen: boolean;

View File

@@ -1,8 +1,6 @@
<template> <template>
<Teleport to="body"> <Modal :is-open="isOpen" @close="close" title="Save project" :initialWidth="400">
<div v-if="isOpen" class="fixed inset-0 z-50 flex items-center justify-center bg-black/50 backdrop-blur-sm" @click.self="close"> <div class="px-2 pt-2 pb-1">
<div class="bg-white dark:bg-gray-800 rounded-xl shadow-xl p-6 w-full max-w-sm border border-gray-200 dark:border-gray-700">
<h2 class="text-xl font-bold mb-4 text-gray-900 dark:text-gray-100">Save project</h2>
<form @submit.prevent="handleSubmit"> <form @submit.prevent="handleSubmit">
<div class="mb-4"> <div class="mb-4">
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">Project Name</label> <label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">Project Name</label>
@@ -14,12 +12,12 @@
</div> </div>
</form> </form>
</div> </div>
</div> </Modal>
</Teleport>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { ref, watch, nextTick } from 'vue'; import { ref, watch, nextTick } from 'vue';
import Modal from '@/components/utilities/Modal.vue';
const props = defineProps<{ const props = defineProps<{
isOpen: boolean; isOpen: boolean;

View File

@@ -0,0 +1,72 @@
<template>
<Modal :is-open="isOpen" @close="close" title="Copy sprite to frame" :initialWidth="380" :initialHeight="320">
<div class="p-4 flex flex-col h-full">
<div class="space-y-4 flex-1">
<div>
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">Target frame</label>
<select v-model.number="targetFrame" class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100 focus:ring-2 focus:ring-blue-500">
<option v-for="i in maxFrameCount" :key="i" :value="i - 1">Frame {{ i }}</option>
</select>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">Target layer</label>
<select v-model="targetLayerId" class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100 focus:ring-2 focus:ring-blue-500">
<option v-for="layer in layers" :key="layer.id" :value="layer.id">{{ layer.name }}</option>
</select>
</div>
</div>
<div class="flex justify-end gap-3 mt-6 pt-4 border-t border-gray-100 dark:border-gray-700">
<button @click="close" class="px-4 py-2 text-gray-600 dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-700 rounded-lg transition-colors font-medium text-sm">Cancel</button>
<button @click="confirm" class="px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors font-medium text-sm">Copy</button>
</div>
</div>
</Modal>
</template>
<script setup lang="ts">
import { ref, computed, watch } from 'vue';
import Modal from './Modal.vue';
import type { Layer } from '@/types/sprites';
const props = defineProps<{
isOpen: boolean;
layers: Layer[];
initialLayerId?: string;
}>();
const emit = defineEmits<{
(e: 'close'): void;
(e: 'copy', targetLayerId: string, targetFrameIndex: number): void;
}>();
const targetFrame = ref(0);
const targetLayerId = ref('');
const maxFrameCount = computed(() => {
const maxLen = Math.max(1, ...props.layers.map(l => l.sprites.length));
return maxLen + 1; // Allow copying to one frame beyond current max
});
watch(
() => props.isOpen,
isOpen => {
if (isOpen) {
targetFrame.value = 0;
targetLayerId.value = props.initialLayerId || (props.layers.length > 0 ? props.layers[0].id : '');
}
}
);
const close = () => {
emit('close');
};
const confirm = () => {
if (targetLayerId.value) {
emit('copy', targetLayerId.value, targetFrame.value);
close();
}
};
</script>

View File

@@ -10,31 +10,39 @@
width: isFullScreen ? '100%' : `${size.width}px`, width: isFullScreen ? '100%' : `${size.width}px`,
height: isFullScreen ? '100%' : `${size.height}px`, height: isFullScreen ? '100%' : `${size.height}px`,
}" }"
class="bg-white dark:bg-gray-800 rounded-2xl border-2 border-gray-300 dark:border-gray-700 shadow-xl flex flex-col fixed z-50 transition-colors duration-300" class="bg-white dark:bg-gray-800 rounded-2xl border border-gray-200 dark:border-gray-700 shadow-2xl flex flex-col fixed z-50 transition-colors duration-300"
:class="{ 'rounded-none border-0': isFullScreen, 'select-none': isDragging }" :class="{ 'rounded-none border-0': isFullScreen, 'select-none': isDragging }"
> >
<!-- Header with drag handle --> <!-- Header with drag handle -->
<div class="flex justify-between items-center p-4 border-b border-gray-200 dark:border-gray-700" :class="{ 'cursor-move': !isFullScreen && !isMobile }" @mousedown="startDrag" @touchstart="handleTouchStart"> <div class="flex justify-between items-center px-5 py-4 border-b border-gray-100 dark:border-gray-700/50" :class="{ 'cursor-move': !isFullScreen && !isMobile }" @mousedown="startDrag" @touchstart="handleTouchStart">
<h3 class="text-xl sm:text-2xl font-semibold text-gray-900 dark:text-gray-100 truncate pr-2">{{ title }}</h3> <h3 class="text-lg sm:text-lg font-bold text-gray-800 dark:text-gray-100 truncate pr-2">{{ title }}</h3>
<div class="flex items-center space-x-2"> <div class="flex items-center space-x-1">
<button @click="toggleFullScreen" @mousedown.stop @touchstart.stop class="p-2 hover:bg-gray-100 dark:hover:bg-gray-700 rounded-lg transition-colors" data-rybbit-event="modal-fullscreen"> <button @click="toggleFullScreen" @mousedown.stop @touchstart.stop class="p-1.5 text-gray-400 hover:text-gray-600 dark:hover:text-gray-200 hover:bg-gray-100 dark:hover:bg-gray-700 rounded-md transition-all" data-rybbit-event="modal-fullscreen">
<img src="@/assets/images/fullscreen-icon.svg" class="w-4 h-4 dark:invert" alt="Fullscreen" :class="{ 'rotate-180': isFullScreen }" /> <svg xmlns="http://www.w3.org/2000/svg" class="w-4 h-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" v-if="!isFullScreen">
<path d="M8 3H5a2 2 0 0 0-2 2v3m18 0V5a2 2 0 0 0-2-2h-3m0 18h3a2 2 0 0 0 2-2v-3M3 16v3a2 2 0 0 0 2 2h3" />
</svg>
<svg xmlns="http://www.w3.org/2000/svg" class="w-4 h-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" v-else>
<path d="M8 3v3a2 2 0 0 1-2 2H3m18 0h-3a2 2 0 0 1-2-2V3m0 18v-3a2 2 0 0 1 2-2h3M3 16h3a2 2 0 0 1 2 2v3" />
</svg>
</button> </button>
<button @click="close" @mousedown.stop @touchstart.stop class="p-2 hover:bg-gray-100 dark:hover:bg-gray-700 rounded-lg transition-colors" data-rybbit-event="modal-close"> <button @click="close" @mousedown.stop @touchstart.stop class="p-1.5 text-gray-400 hover:text-red-500 hover:bg-red-50 dark:hover:bg-red-900/20 rounded-md transition-all" data-rybbit-event="modal-close">
<img src="@/assets/images/close-icon.svg" class="w-5 h-5 dark:invert" alt="Close" /> <svg xmlns="http://www.w3.org/2000/svg" class="w-5 h-5" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<line x1="18" y1="6" x2="6" y2="18"></line>
<line x1="6" y1="6" x2="18" y2="18"></line>
</svg>
</button> </button>
</div> </div>
</div> </div>
<!-- Body --> <!-- Body -->
<div class="p-4 sm:p-6 flex-1 overflow-auto"> <div :class="[noPadding ? '' : 'p-5 sm:p-6', 'flex-1 overflow-auto']">
<slot></slot> <slot></slot>
</div> </div>
<!-- Resize handle --> <!-- Resize handle -->
<div v-if="!isFullScreen && !isMobile" class="absolute bottom-0 right-0 w-8 h-8 cursor-se-resize" @mousedown="startResize" @touchstart="startResize"> <div v-if="!isFullScreen && !isMobile" class="absolute bottom-0 right-0 w-6 h-6 cursor-se-resize flex items-end justify-end p-1 opacity-50 hover:opacity-100 transition-opacity" @mousedown="startResize" @touchstart="startResize">
<svg class="w-8 h-8 text-gray-400 dark:text-gray-500" viewBox="0 0 24 24" fill="currentColor"> <svg class="w-4 h-4 text-gray-400 dark:text-gray-500" viewBox="0 0 24 24" fill="currentColor">
<path d="M22 22H20V20H22V22ZM22 18H20V16H22V18ZM18 22H16V20H18V22ZM22 14H20V12H22V14ZM18 18H16V16H18V18ZM14 22H12V20H14V22Z" /> <path d="M22 22H20V20H22V22ZM22 18H20V16H22V18ZM18 22H16V20H18V22Z" />
</svg> </svg>
</div> </div>
</div> </div>
@@ -49,6 +57,7 @@
title: string; title: string;
initialWidth?: number; initialWidth?: number;
initialHeight?: number; initialHeight?: number;
noPadding?: boolean;
}>(); }>();
const emit = defineEmits<{ const emit = defineEmits<{