[FEAT] Clean code
This commit is contained in:
@@ -13,7 +13,6 @@
|
||||
const breadcrumbs = computed<BreadcrumbItem[]>(() => {
|
||||
const items: BreadcrumbItem[] = [{ name: 'Home', path: '/' }];
|
||||
|
||||
// Map route names to breadcrumb labels
|
||||
const routeLabels: Record<string, string> = {
|
||||
'blog-overview': 'Blog',
|
||||
'blog-detail': 'Blog',
|
||||
@@ -27,10 +26,8 @@
|
||||
const routeName = route.name.toString();
|
||||
|
||||
if (routeName === 'blog-detail') {
|
||||
// For blog detail pages, add Blog first, then the post title
|
||||
items.push({ name: 'Blog', path: '/blog' });
|
||||
|
||||
// Get the post title from route meta or params if available
|
||||
const postTitle = (route.meta.title as string) || 'Article';
|
||||
items.push({ name: postTitle, path: route.path });
|
||||
} else if (routeLabels[routeName]) {
|
||||
|
||||
@@ -86,12 +86,10 @@
|
||||
}
|
||||
|
||||
success.value = 'Thank you! Your feedback was sent.';
|
||||
// Reset fields
|
||||
name.value = '';
|
||||
contact.value = '';
|
||||
content.value = '';
|
||||
|
||||
// Optionally close after short delay
|
||||
setTimeout(() => close(), 600);
|
||||
} catch (e: any) {
|
||||
console.error('Failed to send feedback:', e);
|
||||
|
||||
@@ -63,7 +63,6 @@
|
||||
if (input.files && input.files.length > 0) {
|
||||
const files = Array.from(input.files);
|
||||
emit('uploadSprites', files);
|
||||
// Reset input value so uploading the same file again will trigger the event
|
||||
if (fileInput.value) fileInput.value.value = '';
|
||||
}
|
||||
};
|
||||
|
||||
@@ -170,13 +170,11 @@
|
||||
const response = await fetch('/CHANGELOG.md');
|
||||
const text = await response.text();
|
||||
|
||||
// Configure marked options
|
||||
marked.setOptions({
|
||||
gfm: true, // GitHub Flavored Markdown
|
||||
breaks: true, // Convert line breaks to <br>
|
||||
});
|
||||
|
||||
// Convert markdown to HTML
|
||||
changelogHtml.value = await marked(text);
|
||||
} catch (error) {
|
||||
console.error('Failed to load changelog:', error);
|
||||
|
||||
@@ -87,7 +87,6 @@
|
||||
copied.value = false;
|
||||
}, 2000);
|
||||
} catch {
|
||||
// Fallback for older browsers
|
||||
const input = document.createElement('input');
|
||||
input.value = shareUrl.value;
|
||||
document.body.appendChild(input);
|
||||
@@ -101,14 +100,12 @@
|
||||
}
|
||||
};
|
||||
|
||||
// Start sharing when modal opens
|
||||
watch(
|
||||
() => props.isOpen,
|
||||
isOpen => {
|
||||
if (isOpen) {
|
||||
performShare();
|
||||
} else {
|
||||
// Reset state when closing
|
||||
loading.value = false;
|
||||
shareUrl.value = '';
|
||||
error.value = '';
|
||||
|
||||
@@ -225,7 +225,6 @@
|
||||
(e: 'copySpriteToFrame', spriteId: string, targetLayerId: string, targetFrameIndex: number): void;
|
||||
}>();
|
||||
|
||||
// Get settings from store
|
||||
const settingsStore = useSettingsStore();
|
||||
|
||||
const gridContainerRef = ref<HTMLDivElement | null>(null);
|
||||
@@ -242,6 +241,8 @@
|
||||
};
|
||||
};
|
||||
|
||||
const selectedSpriteIds = ref<Set<string>>(new Set());
|
||||
|
||||
const {
|
||||
isDragging,
|
||||
activeSpriteId,
|
||||
@@ -266,6 +267,7 @@
|
||||
manualCellSizeEnabled: toRef(settingsStore, 'manualCellSizeEnabled'),
|
||||
manualCellWidth: toRef(settingsStore, 'manualCellWidth'),
|
||||
manualCellHeight: toRef(settingsStore, 'manualCellHeight'),
|
||||
selectedSpriteIds,
|
||||
getMousePosition,
|
||||
onUpdateSprite: (id, x, y) => emit('updateSprite', id, x, y),
|
||||
onUpdateSpriteCell: (id, newIndex) => emit('updateSpriteCell', id, newIndex),
|
||||
@@ -287,16 +289,13 @@
|
||||
const contextMenuY = ref(0);
|
||||
const contextMenuIndex = ref<number | null>(null);
|
||||
const contextMenuSpriteId = ref<string | null>(null);
|
||||
const selectedSpriteIds = ref<Set<string>>(new Set());
|
||||
const replacingSpriteId = ref<string | null>(null);
|
||||
const fileInput = ref<HTMLInputElement | null>(null);
|
||||
|
||||
// Copy to frame modal state
|
||||
const showCopyToFrameModal = ref(false);
|
||||
const copyTargetLayerId = ref(props.activeLayerId);
|
||||
const copySpriteId = ref<string | null>(null);
|
||||
|
||||
// Clear selection when toggling multi-select mode
|
||||
watch(
|
||||
() => props.isMultiSelectMode,
|
||||
() => {
|
||||
@@ -304,7 +303,6 @@
|
||||
}
|
||||
);
|
||||
|
||||
// Use the new useGridMetrics composable for consistent calculations
|
||||
const { gridMetrics: gridMetricsRef, getCellPosition: getCellPositionHelper } = useGridMetrics({
|
||||
layers: toRef(props, 'layers'),
|
||||
negativeSpacingEnabled: toRef(settingsStore, 'negativeSpacingEnabled'),
|
||||
@@ -316,13 +314,11 @@
|
||||
const gridMetrics = gridMetricsRef;
|
||||
|
||||
const totalCells = computed(() => {
|
||||
// Use all layers regardless of visibility to keep canvas size stable
|
||||
const maxLen = Math.max(1, ...props.layers.map(l => l.sprites.length));
|
||||
return Math.max(1, Math.ceil(maxLen / props.columns)) * props.columns;
|
||||
});
|
||||
|
||||
const gridDimensions = computed(() => {
|
||||
// Use all layers regardless of visibility to keep canvas size stable
|
||||
const maxLen = Math.max(1, ...props.layers.map(l => l.sprites.length));
|
||||
const rows = Math.max(1, Math.ceil(maxLen / props.columns));
|
||||
return {
|
||||
@@ -335,7 +331,6 @@
|
||||
return getCellPositionHelper(index, props.columns, gridMetrics.value);
|
||||
};
|
||||
|
||||
// Use the new useBackgroundStyles composable for consistent background styling
|
||||
const {
|
||||
backgroundColor: cellBackgroundColor,
|
||||
backgroundImage: cellBackgroundImage,
|
||||
@@ -353,17 +348,14 @@
|
||||
const getCellBackgroundPosition = () => cellBackgroundPosition.value;
|
||||
|
||||
const startDrag = (event: MouseEvent) => {
|
||||
// If the click originated from an interactive element (button, link, input), ignore drag handling
|
||||
const target = event.target as HTMLElement;
|
||||
if (target && target.closest('button, a, input, select, textarea')) {
|
||||
return;
|
||||
}
|
||||
if (!gridContainerRef.value) return;
|
||||
|
||||
// Hide context menu if open
|
||||
showContextMenu.value = false;
|
||||
|
||||
// Handle right-click for context menu
|
||||
if ('button' in event && (event as MouseEvent).button === 2) {
|
||||
event.preventDefault();
|
||||
const pos = getMousePosition(event, props.zoom);
|
||||
@@ -374,14 +366,11 @@
|
||||
contextMenuSpriteId.value = clickedSprite?.id || null;
|
||||
|
||||
if (clickedSprite) {
|
||||
// If the right-clicked sprite is not in the selection, clear selection and select just this one
|
||||
if (!selectedSpriteIds.value.has(clickedSprite.id)) {
|
||||
selectedSpriteIds.value.clear();
|
||||
selectedSpriteIds.value.add(clickedSprite.id);
|
||||
}
|
||||
// If it IS in the selection, keep the current selection (so we can apply action to all)
|
||||
} else {
|
||||
// Right click on empty space
|
||||
selectedSpriteIds.value.clear();
|
||||
}
|
||||
|
||||
@@ -392,37 +381,27 @@
|
||||
return;
|
||||
}
|
||||
|
||||
// Ignore non-left mouse buttons
|
||||
if ('button' in event && (event as MouseEvent).button !== 0) return;
|
||||
|
||||
// Handle selection logic for left click
|
||||
const pos = getMousePosition(event, props.zoom);
|
||||
if (pos) {
|
||||
const clickedSprite = findSpriteAtPosition(pos.x, pos.y);
|
||||
if (clickedSprite) {
|
||||
// Selection logic with multi-select mode check
|
||||
if (event.ctrlKey || event.metaKey || props.isMultiSelectMode) {
|
||||
// Toggle selection
|
||||
if (selectedSpriteIds.value.has(clickedSprite.id)) {
|
||||
selectedSpriteIds.value.delete(clickedSprite.id);
|
||||
} else {
|
||||
if (!selectedSpriteIds.value.has(clickedSprite.id)) {
|
||||
selectedSpriteIds.value.add(clickedSprite.id);
|
||||
}
|
||||
} else {
|
||||
// Single select (but don't clear if dragging starts immediately?
|
||||
// Usually standard behavior is to clear others unless shift/ctrl held)
|
||||
if (!selectedSpriteIds.value.has(clickedSprite.id)) {
|
||||
selectedSpriteIds.value.clear();
|
||||
selectedSpriteIds.value.add(clickedSprite.id);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Clicked on empty space
|
||||
selectedSpriteIds.value.clear();
|
||||
}
|
||||
}
|
||||
|
||||
// Delegate to composable for actual drag handling
|
||||
dragStart(event);
|
||||
};
|
||||
|
||||
@@ -430,7 +409,6 @@
|
||||
const latestEvent = ref<MouseEvent | null>(null);
|
||||
|
||||
const drag = (event: MouseEvent) => {
|
||||
// Store the latest event and schedule a single animation frame update
|
||||
latestEvent.value = event;
|
||||
if (!pendingDrag.value) {
|
||||
pendingDrag.value = true;
|
||||
@@ -481,7 +459,6 @@
|
||||
|
||||
const handleKeyDown = (event: KeyboardEvent) => {
|
||||
if (event.key === 'Delete' || event.key === 'Backspace') {
|
||||
// Don't delete if editing text/input
|
||||
if (document.activeElement?.tagName === 'INPUT' || document.activeElement?.tagName === 'TEXTAREA') return;
|
||||
|
||||
if (selectedSpriteIds.value.size > 0) {
|
||||
@@ -571,8 +548,6 @@
|
||||
document.removeEventListener('mouseup', stopDrag);
|
||||
document.removeEventListener('keydown', handleKeyDown);
|
||||
});
|
||||
|
||||
// Watch for background color changes
|
||||
</script>
|
||||
|
||||
<style scoped></style>
|
||||
|
||||
@@ -303,7 +303,6 @@
|
||||
|
||||
const previewContainerRef = ref<HTMLDivElement | null>(null);
|
||||
|
||||
// Get settings from store
|
||||
const settingsStore = useSettingsStore();
|
||||
|
||||
const {
|
||||
@@ -331,14 +330,12 @@
|
||||
onDraw: () => {}, // No longer needed for canvas drawing
|
||||
});
|
||||
|
||||
// Preview state
|
||||
const isDraggable = ref(false);
|
||||
const repositionAllLayers = ref(false);
|
||||
const arrowKeyMovement = ref(false);
|
||||
const showAllSprites = ref(false);
|
||||
const isDragOver = ref(false);
|
||||
|
||||
// Context menu state
|
||||
const showContextMenu = ref(false);
|
||||
const contextMenuX = ref(0);
|
||||
const contextMenuY = ref(0);
|
||||
@@ -347,11 +344,9 @@
|
||||
const fileInput = ref<HTMLInputElement | null>(null);
|
||||
const replacingSpriteId = ref<string | null>(null);
|
||||
|
||||
// Copy to frame modal state
|
||||
const showCopyToFrameModal = ref(false);
|
||||
const copyTargetLayerId = ref(props.activeLayerId);
|
||||
|
||||
// Drag and drop for new sprites
|
||||
const onDragOver = () => {
|
||||
isDragOver.value = true;
|
||||
};
|
||||
@@ -373,10 +368,8 @@
|
||||
};
|
||||
|
||||
const compositeFrames = computed<Sprite[]>(() => {
|
||||
// Show frames from the active layer for the thumbnail list
|
||||
const activeLayer = props.layers.find(l => l.id === props.activeLayerId);
|
||||
if (!activeLayer) {
|
||||
// Fallback to first visible layer if no active layer
|
||||
const v = getVisibleLayers();
|
||||
const len = maxFrames();
|
||||
const arr: Sprite[] = [];
|
||||
@@ -395,7 +388,6 @@
|
||||
return layer.sprites[currentFrameIndex.value] || null;
|
||||
});
|
||||
|
||||
// Use the new useGridMetrics composable for consistent calculations
|
||||
const { gridMetrics, getCellPosition: getCellPositionHelper } = useGridMetrics({
|
||||
layers: toRef(props, 'layers'),
|
||||
negativeSpacingEnabled: toRef(settingsStore, 'negativeSpacingEnabled'),
|
||||
@@ -404,19 +396,16 @@
|
||||
manualCellHeight: toRef(settingsStore, 'manualCellHeight'),
|
||||
});
|
||||
|
||||
// Helper function to get cell position (same as SpriteCanvas)
|
||||
const getCellPosition = (index: number) => {
|
||||
return getCellPositionHelper(index, props.columns, gridMetrics.value);
|
||||
};
|
||||
|
||||
// Computed cell dimensions (for backward compatibility with existing code)
|
||||
const cellDimensions = computed(() => ({
|
||||
cellWidth: gridMetrics.value.maxWidth,
|
||||
cellHeight: gridMetrics.value.maxHeight,
|
||||
negativeSpacing: gridMetrics.value.negativeSpacing,
|
||||
}));
|
||||
|
||||
// Use the new useBackgroundStyles composable for consistent background styling
|
||||
const {
|
||||
backgroundImage: previewBackgroundImage,
|
||||
backgroundSize: previewBackgroundSize,
|
||||
@@ -429,7 +418,6 @@
|
||||
|
||||
const getPreviewBackgroundImage = () => previewBackgroundImage.value;
|
||||
|
||||
// Dragging state
|
||||
const isDragging = ref(false);
|
||||
const activeSpriteId = ref<string | null>(null);
|
||||
const activeLayerId = ref<string | null>(null);
|
||||
@@ -438,7 +426,6 @@
|
||||
const spritePosBeforeDrag = ref({ x: 0, y: 0 });
|
||||
const allSpritesPosBeforeDrag = ref<Map<string, { x: number; y: number }>>(new Map());
|
||||
|
||||
// Drag functionality
|
||||
const startDrag = (event: MouseEvent, sprite: Sprite, layerId: string) => {
|
||||
if (!isDraggable.value || !previewContainerRef.value) return;
|
||||
|
||||
@@ -455,7 +442,6 @@
|
||||
dragStartX.value = mouseX;
|
||||
dragStartY.value = mouseY;
|
||||
|
||||
// Store initial positions for all sprites in this frame from all visible layers
|
||||
allSpritesPosBeforeDrag.value.clear();
|
||||
const visibleLayers = getVisibleLayers();
|
||||
visibleLayers.forEach(layer => {
|
||||
@@ -490,7 +476,6 @@
|
||||
const { cellWidth, cellHeight, negativeSpacing } = cellDimensions.value;
|
||||
|
||||
if (activeSpriteId.value === 'ALL_LAYERS') {
|
||||
// Move all sprites in current frame from all visible layers
|
||||
const visibleLayers = getVisibleLayers();
|
||||
visibleLayers.forEach(layer => {
|
||||
const sprite = layer.sprites[currentFrameIndex.value];
|
||||
@@ -499,26 +484,21 @@
|
||||
const originalPos = allSpritesPosBeforeDrag.value.get(sprite.id);
|
||||
if (!originalPos) return;
|
||||
|
||||
// Calculate new position with constraints
|
||||
let newX = Math.round(originalPos.x + deltaX);
|
||||
let newY = Math.round(originalPos.y + deltaY);
|
||||
|
||||
// Constrain movement within expanded cell
|
||||
newX = Math.max(-negativeSpacing, Math.min(cellWidth - negativeSpacing - sprite.width, newX));
|
||||
newY = Math.max(-negativeSpacing, Math.min(cellHeight - negativeSpacing - sprite.height, newY));
|
||||
|
||||
emit('updateSpriteInLayer', layer.id, sprite.id, newX, newY);
|
||||
});
|
||||
} else {
|
||||
// Move only the active layer sprite
|
||||
const activeSprite = props.layers.find(l => l.id === activeLayerId.value)?.sprites[currentFrameIndex.value];
|
||||
if (!activeSprite || activeSprite.id !== activeSpriteId.value) return;
|
||||
|
||||
// Calculate new position with constraints and round to integers
|
||||
let newX = Math.round(spritePosBeforeDrag.value.x + deltaX);
|
||||
let newY = Math.round(spritePosBeforeDrag.value.y + deltaY);
|
||||
|
||||
// Constrain movement within expanded cell (allow negative values up to -negativeSpacing)
|
||||
newX = Math.max(-negativeSpacing, Math.min(cellWidth - negativeSpacing - activeSprite.width, newX));
|
||||
newY = Math.max(-negativeSpacing, Math.min(cellHeight - negativeSpacing - activeSprite.height, newY));
|
||||
|
||||
@@ -562,7 +542,6 @@
|
||||
}
|
||||
};
|
||||
|
||||
// Arrow key movement handler
|
||||
const handleKeyDown = (event: KeyboardEvent) => {
|
||||
if (!isDraggable.value || !arrowKeyMovement.value) return;
|
||||
|
||||
@@ -593,7 +572,6 @@
|
||||
const { cellWidth, cellHeight, negativeSpacing } = cellDimensions.value;
|
||||
|
||||
if (repositionAllLayers.value) {
|
||||
// Move all sprites in current frame from all visible layers
|
||||
const visibleLayers = getVisibleLayers();
|
||||
visibleLayers.forEach(layer => {
|
||||
const sprite = layer.sprites[currentFrameIndex.value];
|
||||
@@ -602,14 +580,12 @@
|
||||
let newX = Math.round(sprite.x + deltaX);
|
||||
let newY = Math.round(sprite.y + deltaY);
|
||||
|
||||
// Constrain movement within expanded cell
|
||||
newX = Math.max(-negativeSpacing, Math.min(cellWidth - negativeSpacing - sprite.width, newX));
|
||||
newY = Math.max(-negativeSpacing, Math.min(cellHeight - negativeSpacing - sprite.height, newY));
|
||||
|
||||
emit('updateSpriteInLayer', layer.id, sprite.id, newX, newY);
|
||||
});
|
||||
} else {
|
||||
// Move only the active layer sprite
|
||||
const activeLayer = props.layers.find(l => l.id === props.activeLayerId);
|
||||
if (!activeLayer) return;
|
||||
|
||||
@@ -619,7 +595,6 @@
|
||||
let newX = Math.round(sprite.x + deltaX);
|
||||
let newY = Math.round(sprite.y + deltaY);
|
||||
|
||||
// Constrain movement within expanded cell
|
||||
newX = Math.max(-negativeSpacing, Math.min(cellWidth - negativeSpacing - sprite.width, newX));
|
||||
newY = Math.max(-negativeSpacing, Math.min(cellHeight - negativeSpacing - sprite.height, newY));
|
||||
|
||||
@@ -627,7 +602,6 @@
|
||||
}
|
||||
};
|
||||
|
||||
// Lifecycle hooks
|
||||
onMounted(() => {
|
||||
window.addEventListener('keydown', handleKeyDown);
|
||||
});
|
||||
@@ -637,8 +611,6 @@
|
||||
window.removeEventListener('keydown', handleKeyDown);
|
||||
});
|
||||
|
||||
// Watchers - most canvas-related watchers removed
|
||||
// Keep layer watchers to ensure reactivity
|
||||
watch(
|
||||
() => props.layers,
|
||||
() => {},
|
||||
@@ -650,7 +622,6 @@
|
||||
);
|
||||
watch(currentFrameIndex, () => {});
|
||||
|
||||
// Context menu functions
|
||||
const openContextMenu = (event: MouseEvent, sprite: Sprite, layerId: string) => {
|
||||
event.preventDefault();
|
||||
contextMenuSpriteId.value = sprite.id;
|
||||
|
||||
@@ -115,7 +115,6 @@
|
||||
const settingsStore = useSettingsStore();
|
||||
const splitter = useSpritesheetSplitter();
|
||||
|
||||
// State
|
||||
const detectionMode = ref<DetectionMode>('grid');
|
||||
const cellWidth = ref(64);
|
||||
const cellHeight = ref(64);
|
||||
@@ -126,12 +125,10 @@
|
||||
const isProcessing = ref(false);
|
||||
const imageElement = ref<HTMLImageElement | null>(null);
|
||||
|
||||
// Computed
|
||||
const gridCols = computed(() => (imageElement.value && cellWidth.value > 0 ? Math.floor(imageElement.value.width / cellWidth.value) : 0));
|
||||
|
||||
const gridRows = computed(() => (imageElement.value && cellHeight.value > 0 ? Math.floor(imageElement.value.height / cellHeight.value) : 0));
|
||||
|
||||
// Load image and set initial cell size
|
||||
watch(
|
||||
() => props.imageUrl,
|
||||
url => {
|
||||
@@ -141,7 +138,6 @@
|
||||
img.onload = () => {
|
||||
imageElement.value = img;
|
||||
|
||||
// Set suggested cell size
|
||||
const suggested = splitter.getSuggestedCellSize(img.width, img.height);
|
||||
cellWidth.value = suggested.width;
|
||||
cellHeight.value = suggested.height;
|
||||
@@ -153,14 +149,12 @@
|
||||
{ immediate: true }
|
||||
);
|
||||
|
||||
// Regenerate preview when options change
|
||||
watch([detectionMode, cellWidth, cellHeight, sensitivity, removeEmpty, preserveCellSize], () => {
|
||||
if (imageElement.value) {
|
||||
generatePreview();
|
||||
}
|
||||
});
|
||||
|
||||
// Generate preview
|
||||
async function generatePreview() {
|
||||
if (!imageElement.value) return;
|
||||
|
||||
@@ -190,7 +184,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
// Actions
|
||||
function cancel() {
|
||||
emit('close');
|
||||
}
|
||||
|
||||
@@ -92,7 +92,6 @@
|
||||
close();
|
||||
} catch (e: any) {
|
||||
error.value = e.message || 'An error occurred';
|
||||
// Better PB error handling
|
||||
if (e?.data?.message) error.value = e.data.message;
|
||||
} finally {
|
||||
loading.value = false;
|
||||
|
||||
@@ -74,7 +74,6 @@
|
||||
import { useProjectManager } from '@/composables/useProjectManager';
|
||||
import { useToast } from '@/composables/useToast';
|
||||
|
||||
// Sub-components
|
||||
import NavbarLogo from './navbar/NavbarLogo.vue';
|
||||
import NavbarLinks from './navbar/NavbarLinks.vue';
|
||||
import NavbarProjectActions from './navbar/NavbarProjectActions.vue';
|
||||
@@ -144,7 +143,6 @@
|
||||
} catch (error) {
|
||||
addToast('Failed to save project', 'error');
|
||||
console.error(error);
|
||||
// Error handled in composable but kept here for toast
|
||||
} finally {
|
||||
isSaving.value = false;
|
||||
}
|
||||
|
||||
@@ -5,11 +5,7 @@
|
||||
:key="link.path"
|
||||
:to="link.path"
|
||||
class="px-3 py-2 rounded-lg text-sm font-medium transition-all duration-200"
|
||||
:class="[
|
||||
isActive(link.path)
|
||||
? 'bg-indigo-50 dark:bg-indigo-500/10 text-indigo-600 dark:text-indigo-400'
|
||||
: 'text-gray-600 dark:text-gray-400 hover:bg-gray-50 dark:hover:bg-gray-800 hover:text-indigo-600 dark:hover:text-indigo-400'
|
||||
]"
|
||||
:class="[isActive(link.path) ? 'bg-indigo-50 dark:bg-indigo-500/10 text-indigo-600 dark:text-indigo-400' : 'text-gray-600 dark:text-gray-400 hover:bg-gray-50 dark:hover:bg-gray-800 hover:text-indigo-600 dark:hover:text-indigo-400']"
|
||||
>
|
||||
{{ link.name }}
|
||||
</router-link>
|
||||
@@ -17,22 +13,22 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useRoute } from 'vue-router';
|
||||
import { useRoute } from 'vue-router';
|
||||
|
||||
const route = useRoute();
|
||||
const route = useRoute();
|
||||
|
||||
const links = [
|
||||
{ name: 'Home', path: '/' },
|
||||
{ name: 'Blog', path: '/blog' },
|
||||
{ name: 'About', path: '/about' },
|
||||
{ name: 'FAQ', path: '/faq' },
|
||||
{ name: 'Contact', path: '/contact' },
|
||||
];
|
||||
const links = [
|
||||
{ name: 'Home', path: '/' },
|
||||
{ name: 'Blog', path: '/blog' },
|
||||
{ name: 'About', path: '/about' },
|
||||
{ name: 'FAQ', path: '/faq' },
|
||||
{ name: 'Contact', path: '/contact' },
|
||||
];
|
||||
|
||||
const isActive = (path: string) => {
|
||||
if (path === '/') {
|
||||
return route.path === '/';
|
||||
}
|
||||
return route.path.startsWith(path);
|
||||
};
|
||||
const isActive = (path: string) => {
|
||||
if (path === '/') {
|
||||
return route.path === '/';
|
||||
}
|
||||
return route.path.startsWith(path);
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -39,11 +39,7 @@
|
||||
:key="link.path"
|
||||
:to="link.path"
|
||||
class="flex items-center gap-3 px-3 py-3 rounded-md text-base font-medium transition-all duration-200"
|
||||
:class="[
|
||||
isActive(link.path)
|
||||
? 'bg-indigo-50 dark:bg-indigo-500/10 text-indigo-600 dark:text-indigo-400'
|
||||
: 'text-gray-700 dark:text-gray-200 hover:bg-gray-50 dark:hover:bg-gray-800 hover:text-indigo-600 dark:hover:text-indigo-400'
|
||||
]"
|
||||
:class="[isActive(link.path) ? 'bg-indigo-50 dark:bg-indigo-500/10 text-indigo-600 dark:text-indigo-400' : 'text-gray-700 dark:text-gray-200 hover:bg-gray-50 dark:hover:bg-gray-800 hover:text-indigo-600 dark:hover:text-indigo-400']"
|
||||
@click="$emit('close')"
|
||||
>
|
||||
<i :class="link.icon" class="w-5 text-center"></i> {{ link.name }}
|
||||
@@ -163,6 +159,5 @@
|
||||
|
||||
const handleLogout = () => {
|
||||
authStore.logout();
|
||||
// emit('close'); // Optional: close menu on logout? Maybe better to keep open so they can login again if they want.
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -15,9 +15,9 @@
|
||||
<i class="fas fa-question-circle text-lg"></i>
|
||||
</button>
|
||||
</Tooltip>
|
||||
|
||||
|
||||
<div class="h-4 w-px bg-gray-200 dark:bg-gray-700 ml-1.5"></div>
|
||||
|
||||
|
||||
<DarkModeToggle />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -61,7 +61,6 @@
|
||||
return width.value > 0 && height.value > 0 && columns.value > 0 && rows.value > 0;
|
||||
});
|
||||
|
||||
// Reset to defaults when opened
|
||||
watch(
|
||||
() => props.isOpen,
|
||||
val => {
|
||||
|
||||
@@ -187,9 +187,7 @@
|
||||
const sprites: any[] = [];
|
||||
if (!project.data || !project.data.layers) return sprites;
|
||||
|
||||
// Iterate through layers to find sprites
|
||||
for (const layer of project.data.layers as any[]) {
|
||||
// Check if layer is visible (default to true if undefined)
|
||||
if (layer.visible === false) continue;
|
||||
|
||||
if (layer.sprites && layer.sprites.length > 0) {
|
||||
|
||||
@@ -76,59 +76,47 @@
|
||||
const startPos = ref({ x: 0, y: 0 });
|
||||
const startSize = ref({ width: 0, height: 0 });
|
||||
|
||||
// Add isFullScreen ref
|
||||
const isFullScreen = ref(false);
|
||||
const isMobile = ref(false);
|
||||
|
||||
// Add previous state storage for restoring from full screen
|
||||
const previousState = ref({
|
||||
position: { x: 0, y: 0 },
|
||||
size: { width: 0, height: 0 },
|
||||
});
|
||||
|
||||
// Check if device is mobile
|
||||
const checkMobile = () => {
|
||||
isMobile.value = window.innerWidth < 640; // sm breakpoint in Tailwind
|
||||
|
||||
// Auto fullscreen on mobile
|
||||
if (isMobile.value && !isFullScreen.value) {
|
||||
toggleFullScreen();
|
||||
} else if (!isMobile.value && isFullScreen.value && autoFullScreened.value) {
|
||||
// If we're no longer on mobile and were auto-fullscreened, exit fullscreen
|
||||
toggleFullScreen();
|
||||
autoFullScreened.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
// Track if fullscreen was automatic (for mobile)
|
||||
const autoFullScreened = ref(false);
|
||||
|
||||
// Add toggleFullScreen function
|
||||
const toggleFullScreen = () => {
|
||||
if (!isFullScreen.value) {
|
||||
// Store current state before going full screen
|
||||
previousState.value = {
|
||||
position: { ...position.value },
|
||||
size: { ...size.value },
|
||||
};
|
||||
|
||||
// If toggling to fullscreen on mobile automatically, track it
|
||||
if (isMobile.value) {
|
||||
autoFullScreened.value = true;
|
||||
}
|
||||
} else {
|
||||
// Restore previous state
|
||||
position.value = { ...previousState.value.position };
|
||||
size.value = { ...previousState.value.size };
|
||||
}
|
||||
isFullScreen.value = !isFullScreen.value;
|
||||
};
|
||||
|
||||
// Unified start function for both drag and resize
|
||||
const startAction = (event: MouseEvent | TouchEvent, action: 'drag' | 'resize') => {
|
||||
if (isFullScreen.value) return;
|
||||
|
||||
// Extract the correct coordinates based on event type
|
||||
const clientX = 'touches' in event ? event.touches[0].clientX : event.clientX;
|
||||
const clientY = 'touches' in event ? event.touches[0].clientY : event.clientY;
|
||||
|
||||
@@ -211,7 +199,6 @@
|
||||
position.value = { x: 0, y: 0 };
|
||||
};
|
||||
|
||||
// Event handlers
|
||||
const handleKeyDown = (event: KeyboardEvent) => {
|
||||
if (event.key === 'Escape' && props.isOpen) close();
|
||||
};
|
||||
@@ -223,7 +210,6 @@
|
||||
}
|
||||
};
|
||||
|
||||
// Add these new touch handling functions
|
||||
const handleTouchStart = (event: TouchEvent) => {
|
||||
if (isFullScreen.value) return;
|
||||
if (event.touches.length === 1) {
|
||||
@@ -237,7 +223,6 @@
|
||||
handleMove(event);
|
||||
};
|
||||
|
||||
// Lifecycle
|
||||
watch(
|
||||
() => props.isOpen,
|
||||
newValue => {
|
||||
@@ -250,7 +235,6 @@
|
||||
window.addEventListener('resize', handleResize);
|
||||
window.addEventListener('resize', checkMobile);
|
||||
|
||||
// Initial check for mobile
|
||||
checkMobile();
|
||||
|
||||
if (props.isOpen) centerModal();
|
||||
|
||||
@@ -27,7 +27,6 @@
|
||||
import type { Toast } from '@/composables/useToast';
|
||||
import { useToast } from '@/composables/useToast';
|
||||
|
||||
// Simple functional components for icons using h (avoid runtime compiler)
|
||||
const SuccessIcon = {
|
||||
render: () =>
|
||||
h(
|
||||
|
||||
@@ -42,30 +42,24 @@
|
||||
let x = mouseX.value + offsetX;
|
||||
let y = mouseY.value + offsetY;
|
||||
|
||||
// Get tooltip dimensions (estimate if not mounted yet)
|
||||
const tooltipWidth = tooltipRef.value?.offsetWidth || 200;
|
||||
const tooltipHeight = tooltipRef.value?.offsetHeight || 30;
|
||||
|
||||
// Screen boundaries
|
||||
const screenWidth = window.innerWidth;
|
||||
const screenHeight = window.innerHeight;
|
||||
|
||||
// Adjust horizontal position if too close to right edge
|
||||
if (x + tooltipWidth + padding > screenWidth) {
|
||||
x = mouseX.value - tooltipWidth - offsetX;
|
||||
}
|
||||
|
||||
// Adjust horizontal position if too close to left edge
|
||||
if (x < padding) {
|
||||
x = padding;
|
||||
}
|
||||
|
||||
// Adjust vertical position if too close to bottom edge
|
||||
if (y + tooltipHeight + padding > screenHeight) {
|
||||
y = mouseY.value - tooltipHeight - offsetY;
|
||||
}
|
||||
|
||||
// Adjust vertical position if too close to top edge
|
||||
if (y < padding) {
|
||||
y = padding;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user