feat(devtool): 脚本录制器自动载入上次打开文件夹、代码

This commit is contained in:
XcantloadX 2025-02-04 17:12:25 +08:00
parent 7d32c43051
commit 5fad34bd6f
2 changed files with 126 additions and 4 deletions

View File

@ -17,6 +17,7 @@ import useHotkey from '../../hooks/useHotkey';
import { useFormModal } from '../../hooks/useFormModal';
import { openDirectory } from '../../utils/fileUtils';
import { useToast } from '../../components/ToastMessage';
import { ScriptRecorderStorage } from '../../utils/storageUtils';
// 引入 ace 编辑器的主题和语言模式
import 'ace-builds/src-noconflict/mode-python';
@ -77,10 +78,11 @@ ContextStackVars.screenshot_mode = 'manual'
device.screenshot()
`
console.log('ScriptRecorderStorage.load()', ScriptRecorderStorage.loadCode());
const useScriptRecorderStore = create<ScriptRecorderState>((set) => ({
code: DEFAULT_CODE,
code: ScriptRecorderStorage.loadCode() || DEFAULT_CODE,
tool: 'drag',
autoScreenshot: true,
connected: false,
imageUrl: '',
@ -92,7 +94,11 @@ const useScriptRecorderStore = create<ScriptRecorderState>((set) => ({
imageMetaDataObject: null,
setImageMetaDataObject: (imageMetaData) => set({ imageMetaDataObject: imageMetaData }),
setCode: (code) => set({ code }),
setCode: (code) => {
ScriptRecorderStorage.saveCode(code);
console.log('setCode', code);
set({ code });
},
setTool: (tool) => set({ tool }),
setAutoScreenshot: (auto) => set({ autoScreenshot: auto }),
setConnected: (connected) => set({ connected }),
@ -395,6 +401,7 @@ function useStoreImageMetaData() {
const ScriptRecorder: React.FC = () => {
const client = useDebugClient();
const { showToast, ToastComponent } = useToast();
const editorRef = useRef<any>(null);
const { imageMetaData, Definitions, Annotations, clear } = useStoreImageMetaData();
@ -580,11 +587,35 @@ const ScriptRecorder: React.FC = () => {
const handleOpenDirectory = async () => {
const handle = await openDirectory();
setDirectoryHandle(handle);
if (handle) {
setDirectoryHandle(handle);
await ScriptRecorderStorage.saveDirectoryHandle(handle);
showToast('success', '', `已载入上次打开文件夹 ${handle.name}`);
}
};
useEffect(() => {
const loadSavedDirectoryHandle = async () => {
try {
const handle = await ScriptRecorderStorage.loadDirectoryHandle();
if (handle) {
const hasPermission = await ScriptRecorderStorage.verifyDirectoryHandlePermission(handle);
if (hasPermission) {
setDirectoryHandle(handle);
showToast('success', '已载入文件夹', `已载入上次打开文件夹 ${handle.name}`);
}
}
} catch (e) {
console.error('Failed to load saved directory handle:', e);
}
};
loadSavedDirectoryHandle();
}, [showToast]);
return (
<Container>
{ToastComponent}
{formModal}
{inEditMode ? (
<EditToolBar

View File

@ -0,0 +1,91 @@
interface ScriptRecorderData {
code: string;
directoryHandle?: FileSystemDirectoryHandle;
}
interface StorageData {
last: ScriptRecorderData;
}
export class ScriptRecorderStorage {
private static readonly STORAGE_KEY = 'scriptRecorder';
private static readonly DB_NAME = 'scriptRecorderDB';
private static readonly STORE_NAME = 'fileHandles';
private static readonly DB_VERSION = 1;
static saveCode(code: string): void {
const data: StorageData = {
last: { code }
};
localStorage.setItem(this.STORAGE_KEY, JSON.stringify(data));
}
static loadCode(): string | null {
const dataStr = localStorage.getItem(this.STORAGE_KEY);
if (!dataStr) return null;
try {
const data = JSON.parse(dataStr) as StorageData;
return data.last.code;
} catch (e) {
console.error('Failed to parse script recorder data:', e);
return null;
}
}
static clearCode(): void {
localStorage.removeItem(this.STORAGE_KEY);
}
private static async getDB(): Promise<IDBDatabase> {
return new Promise((resolve, reject) => {
const request = indexedDB.open(this.DB_NAME, this.DB_VERSION);
request.onerror = () => reject(request.error);
request.onsuccess = () => resolve(request.result);
request.onupgradeneeded = (event) => {
const db = (event.target as IDBOpenDBRequest).result;
if (!db.objectStoreNames.contains(this.STORE_NAME)) {
db.createObjectStore(this.STORE_NAME);
}
};
});
}
static async saveDirectoryHandle(handle: FileSystemDirectoryHandle): Promise<void> {
const db = await this.getDB();
return new Promise((resolve, reject) => {
const transaction = db.transaction(this.STORE_NAME, 'readwrite');
const store = transaction.objectStore(this.STORE_NAME);
const request = store.put(handle, 'directoryHandle');
request.onerror = () => reject(request.error);
request.onsuccess = () => resolve();
});
}
static async loadDirectoryHandle(): Promise<FileSystemDirectoryHandle | null> {
const db = await this.getDB();
return new Promise((resolve, reject) => {
const transaction = db.transaction(this.STORE_NAME, 'readonly');
const store = transaction.objectStore(this.STORE_NAME);
const request = store.get('directoryHandle');
request.onerror = () => reject(request.error);
request.onsuccess = () => resolve(request.result || null);
});
}
static async verifyDirectoryHandlePermission(handle: FileSystemDirectoryHandle): Promise<boolean> {
try {
const options = { mode: 'readwrite' } as const;
// @ts-ignore
const permission = await handle.requestPermission(options);
return permission === 'granted';
} catch (e) {
console.error('Failed to verify directory handle permission:', e);
return false;
}
}
}