2 Commits

Author SHA1 Message Date
a9e998b603 chore: bump version to 0.1.1 2025-12-19 20:51:13 +08:00
75c26d479a feat(文件管理): 添加已打开文件状态标记及相应逻辑
引入hasOpenedFile状态标记,用于跟踪用户是否已打开或操作过文件
- 修改欢迎界面显示逻辑,仅在无内容且未打开文件时显示
- 调整保存/另存为/新建场景等操作的权限判断
- 更新标题栏显示逻辑,支持已打开文件但无内容的场景
- 在文件拖放、加载等操作时自动标记文件打开状态
2025-12-19 20:46:30 +08:00
7 changed files with 82 additions and 20 deletions

View File

@@ -1,7 +1,7 @@
{ {
"name": "refvroid", "name": "refvroid",
"private": true, "private": true,
"version": "0.1.0", "version": "0.1.1",
"type": "module", "type": "module",
"scripts": { "scripts": {
"dev": "vite", "dev": "vite",

2
src-tauri/Cargo.lock generated
View File

@@ -3555,7 +3555,7 @@ dependencies = [
[[package]] [[package]]
name = "refvroid" name = "refvroid"
version = "0.1.0" version = "0.1.1"
dependencies = [ dependencies = [
"arboard", "arboard",
"base64 0.22.1", "base64 0.22.1",

View File

@@ -1,6 +1,6 @@
[package] [package]
name = "refvroid" name = "refvroid"
version = "0.1.0" version = "0.1.1"
description = "RefVroid - Animation Reference Desktop App" description = "RefVroid - Animation Reference Desktop App"
authors = ["you"] authors = ["you"]
edition = "2021" edition = "2021"

View File

@@ -1,7 +1,7 @@
{ {
"$schema": "https://schema.tauri.app/config/2", "$schema": "https://schema.tauri.app/config/2",
"productName": "RefVroid", "productName": "RefVroid",
"version": "0.1.0", "version": "0.1.1",
"identifier": "com.refvroid.app", "identifier": "com.refvroid.app",
"build": { "build": {
"beforeDevCommand": "pnpm dev", "beforeDevCommand": "pnpm dev",

View File

@@ -59,6 +59,7 @@ const state = reactive({
showOpenSceneDialog: false, showOpenSceneDialog: false,
pendingScenePath: null as string | null, pendingScenePath: null as string | null,
pendingAction: null as 'close' | 'openScene' | null, pendingAction: null as 'close' | 'openScene' | null,
hasOpenedFile: false, // 标记是否已打开过文件或添加过内容
}); });
// Computed title // Computed title
@@ -71,6 +72,12 @@ const hasContent = computed(() => {
return sceneManager.elements.value.length > 0; return sceneManager.elements.value.length > 0;
}); });
// Check if should show welcome overlay
// Only show when: no content AND no file has been opened/used
const showWelcome = computed(() => {
return !hasContent.value && !state.hasOpenedFile;
});
// Selection box state // Selection box state
const selectionBox = reactive({ const selectionBox = reactive({
visible: false, visible: false,
@@ -125,7 +132,9 @@ function getContextMenuItems(): MenuItem[] {
items.push({ label: '', action: () => {}, separator: true }); items.push({ label: '', action: () => {}, separator: true });
// File operations // File operations
const canSave = hasContent.value; // 允许保存:有内容 或 已打开文件(可以保存空文件)
const canSave = hasContent.value || state.hasOpenedFile;
const canCreateNew = hasContent.value || state.hasOpenedFile;
items.push( items.push(
{ {
@@ -148,7 +157,7 @@ function getContextMenuItems(): MenuItem[] {
action: () => handleNew(), action: () => handleNew(),
shortcut: 'Ctrl+N', shortcut: 'Ctrl+N',
checked: false, checked: false,
disabled: !canSave, disabled: !canCreateNew,
} }
); );
@@ -212,6 +221,9 @@ async function handleCanvasInitialized() {
try { try {
const loaded = await sceneService.loadOnStartup(); const loaded = await sceneService.loadOnStartup();
if (loaded) { if (loaded) {
// 标记已打开文件
state.hasOpenedFile = true;
// Sync renderer and view transform after loading // Sync renderer and view transform after loading
canvasViewRef.value?.syncRendererElements(); canvasViewRef.value?.syncRendererElements();
canvasViewRef.value?.syncViewTransform(); canvasViewRef.value?.syncViewTransform();
@@ -305,6 +317,9 @@ async function handleTauriFileDrop(paths: string[], dropX: number, dropY: number
// Handle image files // Handle image files
if (imageFiles.length === 0) return; if (imageFiles.length === 0) return;
// 标记已开始使用场景,防止删除元素后显示欢迎界面
state.hasOpenedFile = true;
// Record state for undo // Record state for undo
const currentState = sceneManager.getState(); const currentState = sceneManager.getState();
historyManager.record(currentState.elements, currentState.viewTransform); historyManager.record(currentState.elements, currentState.viewTransform);
@@ -335,6 +350,9 @@ async function loadSceneFromPath(path: string) {
try { try {
const success = await sceneService.loadFromPath(path); const success = await sceneService.loadFromPath(path);
if (success) { if (success) {
// 标记已打开文件,防止删除元素后显示欢迎界面
state.hasOpenedFile = true;
canvasViewRef.value?.syncRendererElements(); canvasViewRef.value?.syncRendererElements();
canvasViewRef.value?.syncViewTransform(); canvasViewRef.value?.syncViewTransform();
canvasViewRef.value?.getRenderer()?.requestRender(); canvasViewRef.value?.getRenderer()?.requestRender();
@@ -355,8 +373,12 @@ async function loadSceneFromPath(path: string) {
async function handleConfirmOpenScene() { async function handleConfirmOpenScene() {
state.showOpenSceneDialog = false; state.showOpenSceneDialog = false;
if (state.pendingScenePath) { if (state.pendingScenePath) {
// 从指定路径加载(拖放文件)
await loadSceneFromPath(state.pendingScenePath); await loadSceneFromPath(state.pendingScenePath);
state.pendingScenePath = null; state.pendingScenePath = null;
} else {
// 通过对话框选择文件Ctrl+L
await performLoad();
} }
} }
@@ -553,8 +575,8 @@ function handleGlobalAction(action: string, data: unknown) {
// ============================================ // ============================================
async function handleSave() { async function handleSave() {
// Don't save if no content // 允许保存:有内容 或 已打开文件(可以保存空文件)
if (!hasContent.value) return; if (!hasContent.value && !state.hasOpenedFile) return;
try { try {
const success = await sceneService.save(); const success = await sceneService.save();
@@ -567,8 +589,8 @@ async function handleSave() {
} }
async function handleSaveAs() { async function handleSaveAs() {
// Don't save if no content // 允许另存为:有内容 或 已打开文件(可以保存空文件)
if (!hasContent.value) return; if (!hasContent.value && !state.hasOpenedFile) return;
try { try {
const success = await sceneService.saveAs(); const success = await sceneService.saveAs();
@@ -581,6 +603,29 @@ async function handleSaveAs() {
} }
async function handleLoad() { async function handleLoad() {
// 如果有未保存的更改,显示提示对话框
if (state.isDirty && (hasContent.value || state.hasOpenedFile)) {
state.pendingAction = 'openScene';
state.pendingScenePath = null; // null 表示通过对话框选择文件
state.showUnsavedDialog = true;
return;
}
// 如果有内容但没有未保存更改,显示确认对话框
if (hasContent.value || state.hasOpenedFile) {
state.pendingScenePath = null;
state.showOpenSceneDialog = true;
return;
}
// 没有内容,直接打开
await performLoad();
}
/**
* 实际执行加载操作
*/
async function performLoad() {
try { try {
// Record state for undo before loading // Record state for undo before loading
const currentState = sceneManager.getState(); const currentState = sceneManager.getState();
@@ -588,6 +633,9 @@ async function handleLoad() {
const success = await sceneService.load(); const success = await sceneService.load();
if (success) { if (success) {
// 标记已打开文件,防止显示欢迎界面
state.hasOpenedFile = true;
// Sync renderer and view transform after loading // Sync renderer and view transform after loading
canvasViewRef.value?.syncRendererElements(); canvasViewRef.value?.syncRendererElements();
canvasViewRef.value?.syncViewTransform(); canvasViewRef.value?.syncViewTransform();
@@ -619,14 +667,18 @@ async function handleBrowseFiles() {
} }
function handleNew() { function handleNew() {
// Don't create new scene if already empty // Don't create new scene if already empty and no file opened
if (!hasContent.value) return; if (!hasContent.value && !state.hasOpenedFile) return;
// Record state for undo // Record state for undo if there's content
const currentState = sceneManager.getState(); if (hasContent.value) {
historyManager.record(currentState.elements, currentState.viewTransform); const currentState = sceneManager.getState();
historyManager.record(currentState.elements, currentState.viewTransform);
}
sceneService.newScene(); sceneService.newScene();
// 重置标志,允许显示欢迎界面
state.hasOpenedFile = false;
canvasViewRef.value?.syncRendererElements(); canvasViewRef.value?.syncRendererElements();
} }
@@ -1005,8 +1057,14 @@ async function executePendingAction() {
if (action === 'close') { if (action === 'close') {
await forceCloseWindow(); await forceCloseWindow();
} else if (action === 'openScene' && scenePath) { } else if (action === 'openScene') {
await loadSceneFromPath(scenePath); if (scenePath) {
// 从指定路径加载(拖放文件)
await loadSceneFromPath(scenePath);
} else {
// 通过对话框选择文件Ctrl+L
await performLoad();
}
} }
} }
@@ -1140,6 +1198,7 @@ onUnmounted(() => {
:always-on-top="state.alwaysOnTop" :always-on-top="state.alwaysOnTop"
:is-dirty="state.isDirty" :is-dirty="state.isDirty"
:has-content="hasContent" :has-content="hasContent"
:has-opened-file="state.hasOpenedFile"
@minimize="minimizeWindow" @minimize="minimizeWindow"
@maximize="toggleFullscreen" @maximize="toggleFullscreen"
@close="closeWindow" @close="closeWindow"
@@ -1156,9 +1215,10 @@ onUnmounted(() => {
@file-drop="handleFileDrop" @file-drop="handleFileDrop"
/> />
<!-- Welcome Overlay (when scene is empty) --> <!-- Welcome Overlay (when scene is empty and no file opened) -->
<WelcomeOverlay <WelcomeOverlay
v-if="state.isInitialized && !hasContent" v-if="state.isInitialized && showWelcome"
:key="'welcome-' + hasContent + '-' + state.hasOpenedFile"
@browse="handleBrowseFiles" @browse="handleBrowseFiles"
@help="state.showHelp = true" @help="state.showHelp = true"
/> />

View File

@@ -502,6 +502,7 @@ async function handleDelete() {
if (sceneManager.getSelectedCount() > 0) { if (sceneManager.getSelectedCount() > 0) {
recordState(); recordState();
sceneManager.deleteSelected(); sceneManager.deleteSelected();
sceneService.markDirty();
await syncRendererElements(); await syncRendererElements();
// Force render update // Force render update
renderer?.requestRender(); renderer?.requestRender();

View File

@@ -10,6 +10,7 @@ const props = defineProps<{
alwaysOnTop?: boolean; alwaysOnTop?: boolean;
isDirty?: boolean; isDirty?: boolean;
hasContent?: boolean; hasContent?: boolean;
hasOpenedFile?: boolean;
}>(); }>();
const emit = defineEmits<{ const emit = defineEmits<{
@@ -133,7 +134,7 @@ onUnmounted(() => {
@mousedown="handleDragStart" @mousedown="handleDragStart"
> >
<span class="title-bar__title"> <span class="title-bar__title">
<template v-if="hasContent">{{ isDirty ? '*' : '' }}{{ title || 'Untitled' }}</template> <template v-if="hasContent || hasOpenedFile">{{ isDirty ? '*' : '' }}{{ title || 'Untitled' }}</template>
</span> </span>
</div> </div>