From ceec0510aed376ec5be7f11b9c5780b0d234a76a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20Tr=C4=99bacz?= Date: Tue, 11 Feb 2025 22:09:20 +0100 Subject: [PATCH] Version 1.2.3, save states ca now be reordered by dragging and dropping the rows --- CHANGELOG.md | 6 ++ package-lock.json | 56 +++++++++- package.json | 5 +- src-tauri/Cargo.lock | 2 +- src-tauri/Cargo.toml | 2 +- src-tauri/tauri.conf.json | 2 +- src/components/SaveStates.tsx | 198 ++++++++++++++++++++++++++++++++++ src/modules/Field.tsx | 88 +-------------- src/useSaveStates.ts | 30 +++++- updater.json | 8 +- 10 files changed, 300 insertions(+), 97 deletions(-) create mode 100644 src/components/SaveStates.tsx diff --git a/CHANGELOG.md b/CHANGELOG.md index d7c6151..3d14ecf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Change Log +## 1.2.3 (2025-02-11) + +### Field + +* Save states can now be reodered by dragging and dropping the rows + ## 1.2.2 (2025-02-11) ### General diff --git a/package-lock.json b/package-lock.json index 7ec25fb..93f8c64 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,13 +1,16 @@ { "name": "ff7-ultima", - "version": "1.1.5", + "version": "1.2.2", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "ff7-ultima", - "version": "1.1.5", + "version": "1.2.2", "dependencies": { + "@dnd-kit/core": "^6.3.1", + "@dnd-kit/sortable": "^10.0.0", + "@dnd-kit/utilities": "^3.2.2", "@radix-ui/react-checkbox": "^1.1.3", "@radix-ui/react-dialog": "^1.1.4", "@radix-ui/react-dropdown-menu": "^2.1.4", @@ -401,6 +404,55 @@ "node": ">=6.9.0" } }, + "node_modules/@dnd-kit/accessibility": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@dnd-kit/accessibility/-/accessibility-3.1.1.tgz", + "integrity": "sha512-2P+YgaXF+gRsIihwwY1gCsQSYnu9Zyj2py8kY5fFvUM1qm2WA2u639R6YNVfU4GWr+ZM5mqEsfHZZLoRONbemw==", + "dependencies": { + "tslib": "^2.0.0" + }, + "peerDependencies": { + "react": ">=16.8.0" + } + }, + "node_modules/@dnd-kit/core": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/@dnd-kit/core/-/core-6.3.1.tgz", + "integrity": "sha512-xkGBRQQab4RLwgXxoqETICr6S5JlogafbhNsidmrkVv2YRs5MLwpjoF2qpiGjQt8S9AoxtIV603s0GIUpY5eYQ==", + "dependencies": { + "@dnd-kit/accessibility": "^3.1.1", + "@dnd-kit/utilities": "^3.2.2", + "tslib": "^2.0.0" + }, + "peerDependencies": { + "react": ">=16.8.0", + "react-dom": ">=16.8.0" + } + }, + "node_modules/@dnd-kit/sortable": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/@dnd-kit/sortable/-/sortable-10.0.0.tgz", + "integrity": "sha512-+xqhmIIzvAYMGfBYYnbKuNicfSsk4RksY2XdmJhT+HAC01nix6fHCztU68jooFiMUB01Ky3F0FyOvhG/BZrWkg==", + "dependencies": { + "@dnd-kit/utilities": "^3.2.2", + "tslib": "^2.0.0" + }, + "peerDependencies": { + "@dnd-kit/core": "^6.3.0", + "react": ">=16.8.0" + } + }, + "node_modules/@dnd-kit/utilities": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/@dnd-kit/utilities/-/utilities-3.2.2.tgz", + "integrity": "sha512-+MKAJEOfaBe5SmV6t34p80MMKhjvUz0vRrvVJbPT0WElzaOJ/1xs+D+KDv+tD/NE5ujfrChEcshd4fLn0wpiqg==", + "dependencies": { + "tslib": "^2.0.0" + }, + "peerDependencies": { + "react": ">=16.8.0" + } + }, "node_modules/@esbuild/aix-ppc64": { "version": "0.21.5", "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", diff --git a/package.json b/package.json index d986eec..6b74741 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "ff7-ultima", "private": true, - "version": "1.2.2", + "version": "1.2.3", "type": "module", "scripts": { "dev": "vite", @@ -10,6 +10,9 @@ "tauri": "tauri" }, "dependencies": { + "@dnd-kit/core": "^6.3.1", + "@dnd-kit/sortable": "^10.0.0", + "@dnd-kit/utilities": "^3.2.2", "@radix-ui/react-checkbox": "^1.1.3", "@radix-ui/react-dialog": "^1.1.4", "@radix-ui/react-dropdown-menu": "^2.1.4", diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index a394366..9d69cb0 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -806,7 +806,7 @@ dependencies = [ [[package]] name = "ff7-ultima" -version = "1.2.2" +version = "1.2.3" dependencies = [ "ff7-lib", "serde", diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index 20a0882..f6eb5f9 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "ff7-ultima" -version = "1.2.2" +version = "1.2.3" description = "Real-time editor for Final Fantasy VII" authors = ["m4v3r"] edition = "2021" diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json index 3b15bba..56de3ef 100644 --- a/src-tauri/tauri.conf.json +++ b/src-tauri/tauri.conf.json @@ -1,6 +1,6 @@ { "productName": "FF7 Ultima", - "version": "1.2.2", + "version": "1.2.3", "identifier": "org.m4v3r.ff7-ultima", "build": { "beforeDevCommand": "npm run dev", diff --git a/src/components/SaveStates.tsx b/src/components/SaveStates.tsx new file mode 100644 index 0000000..08af572 --- /dev/null +++ b/src/components/SaveStates.tsx @@ -0,0 +1,198 @@ +import { Button } from "@/components/ui/button"; +import { EditPopover } from "@/components/EditPopover"; +import { SaveState } from "@/useSaveStates"; +import { FF7 } from "@/useFF7"; +import { useState } from "react"; +import { DndContext, DragEndEvent, MouseSensor, TouchSensor, useSensor, useSensors } from "@dnd-kit/core"; +import { SortableContext, verticalListSortingStrategy, useSortable } from "@dnd-kit/sortable"; +import { CSS } from "@dnd-kit/utilities"; + +interface SaveStatesProps { + ff7: FF7; +} + +interface SaveStateRowProps { + state: SaveState; + index: number; + onLoad: () => void; + onDelete: () => void; + onTitleChange: (title: string) => void; + titleEditOpen: boolean; + titleEditValue: string; + onTitleEditOpenChange: (open: boolean) => void; + onTitleEditValueChange: (value: string) => void; +} + +function SaveStateRow({ + state, + index, + onLoad, + onDelete, + onTitleChange, + titleEditOpen, + titleEditValue, + onTitleEditOpenChange, + onTitleEditValueChange +}: SaveStateRowProps) { + const { + attributes, + listeners, + setNodeRef, + transform, + transition, + isDragging + } = useSortable({ id: state.timestamp.toString() }); + + const style = { + transform: CSS.Transform.toString(transform), + transition, + opacity: isDragging ? 0.5 : 1, + cursor: 'move' + }; + + return ( + + + { + onTitleChange(titleEditValue); + }} + > +
+ {state.title ? ( + {state.title} + ) : ( + {new Date(state.timestamp).toLocaleString('en-US', { year: 'numeric', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit', second: '2-digit', hour12: false }).replace(/(\d+)\/(\d+)\/(\d+)/, '$3-$1-$2')} + )} +
+
+ + + {state.fieldId} + + ({state.fieldName}) + + + + + + + + ); +} + +export function SaveStates({ ff7 }: SaveStatesProps) { + const [titleEditOpen, setTitleEditOpen] = useState(false); + const [titleEditIndex, setTitleEditIndex] = useState(null); + const [titleEditValue, setTitleEditValue] = useState(""); + + const sensors = useSensors( + useSensor(MouseSensor, { + activationConstraint: { + distance: 8, + }, + }), + useSensor(TouchSensor, { + activationConstraint: { + delay: 200, + tolerance: 8, + }, + }) + ); + + const handleDragEnd = (event: DragEndEvent) => { + const { active, over } = event; + + if (over && active.id !== over.id) { + const oldIndex = ff7.saveStates.fieldStates.findIndex( + state => state.timestamp.toString() === active.id + ); + const newIndex = ff7.saveStates.fieldStates.findIndex( + state => state.timestamp.toString() === over.id + ); + + ff7.saveStates.reorderFieldStates(oldIndex, newIndex); + } + }; + + return ( +
+
+

+ Save states +

+ +
+ + {ff7.saveStates.fieldStates.length > 0 ? ( +
+ + + + + + + + + + + state.timestamp.toString())} + strategy={verticalListSortingStrategy} + > + {ff7.saveStates.fieldStates.map((state: SaveState, index: number) => ( + ff7.loadState(index)} + onDelete={() => ff7.saveStates.removeFieldState(index)} + onTitleChange={(title) => { + ff7.saveStates.updateFieldStateTitle(index, title); + setTitleEditOpen(false); + }} + titleEditOpen={titleEditOpen && titleEditIndex === index} + titleEditValue={titleEditValue} + onTitleEditOpenChange={(open) => { + setTitleEditOpen(open); + if (open) { + setTitleEditIndex(index); + setTitleEditValue(state.title || ""); + } + }} + onTitleEditValueChange={setTitleEditValue} + /> + ))} + + + +
TitleField 
+
Click on the save title to rename it • Drag rows to reorder
+
+ ) : ( +
No save states found
+ )} +
+ ); +} \ No newline at end of file diff --git a/src/modules/Field.tsx b/src/modules/Field.tsx index f4979c0..dceda7c 100644 --- a/src/modules/Field.tsx +++ b/src/modules/Field.tsx @@ -11,7 +11,7 @@ import { TooltipTrigger, } from "@/components/ui/tooltip"; import { EditPopover } from "@/components/EditPopover"; -import { SaveState } from "@/useSaveStates"; +import { SaveStates } from "@/components/SaveStates"; export function Field(props: { ff7: FF7 }) { const ff7 = props.ff7; @@ -22,9 +22,6 @@ export function Field(props: { ff7: FF7 }) { const [editCoord, setEditCoord] = useState<"x" | "y" | "z" | "direction" | null>(null); const [popoverOpen, setPopoverOpen] = useState(false); const [currentModelEditing, setCurrentModelEditing] = useState(null); - const [titleEditIndex, setTitleEditIndex] = useState(null); - const [titleEditOpen, setTitleEditOpen] = useState(false); - const [titleEditValue, setTitleEditValue] = useState(""); const openWarpModal = () => { setIsWarpModalOpen(true); @@ -114,88 +111,7 @@ export function Field(props: { ff7: FF7 }) { ff7={ff7} /> -
-

- Save states -

- -
- - {ff7.saveStates.fieldStates.length > 0 ? ( -
- - - - - - - - - - {ff7.saveStates.fieldStates.map((state: SaveState, index: number) => ( - - - - - - ))} - -
TitleField 
- { - setTitleEditOpen(open); - if (open) { - setTitleEditIndex(index); - setTitleEditValue(state.title || ""); - } - }} - value={titleEditValue} - onValueChange={setTitleEditValue} - onSubmit={() => { - ff7.saveStates.updateFieldStateTitle(index, titleEditValue); - setTitleEditOpen(false); - }} - > -
- {state.title ? ( - {state.title} - ) : ( - {new Date(state.timestamp).toLocaleString('en-US', { year: 'numeric', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit', second: '2-digit', hour12: false }).replace(/(\d+)\/(\d+)\/(\d+)/, '$3-$1-$2')} - )} -
-
-
- {state.fieldId} - - ({state.fieldName}) - - - - -
-
Click on the save title to rename it
-
- ) : ( -
No save states found
- )} + {state.fieldModels.length > 0 && state.fieldModels[0] && ( <> diff --git a/src/useSaveStates.ts b/src/useSaveStates.ts index 38fa570..1bdc838 100644 --- a/src/useSaveStates.ts +++ b/src/useSaveStates.ts @@ -132,6 +132,33 @@ export function useSaveStates() { }); }; + const reorderFieldStates = (fromIndex: number, toIndex: number) => { + setFieldStates(prev => { + const newStates = [...prev]; + const [removed] = newStates.splice(fromIndex, 1); + newStates.splice(toIndex, 0, removed); + + // Update lastLoadedFieldStateIndex if needed + if (lastLoadedFieldStateIndex === fromIndex) { + setLastLoadedFieldStateIndex(toIndex); + } else if ( + lastLoadedFieldStateIndex !== null && + fromIndex < lastLoadedFieldStateIndex && + toIndex >= lastLoadedFieldStateIndex + ) { + setLastLoadedFieldStateIndex(lastLoadedFieldStateIndex - 1); + } else if ( + lastLoadedFieldStateIndex !== null && + fromIndex > lastLoadedFieldStateIndex && + toIndex <= lastLoadedFieldStateIndex + ) { + setLastLoadedFieldStateIndex(lastLoadedFieldStateIndex + 1); + } + + return newStates; + }); + }; + return { fieldStates, snowboardStates, @@ -147,6 +174,7 @@ export function useSaveStates() { clearSnowboardStates, clearAllStates, updateFieldStateTitle, - updateSnowboardStateTitle + updateSnowboardStateTitle, + reorderFieldStates }; } \ No newline at end of file diff --git a/updater.json b/updater.json index 9626c64..1d12c5d 100644 --- a/updater.json +++ b/updater.json @@ -1,11 +1,11 @@ { - "version": "1.2.2", - "notes": "Added universal minigame skip, fixed FMV softlock in Junon, made window resizable, improved field warping performance", + "version": "1.2.3", + "notes": "Save states can now be reordered by dragging and dropping the rows", "pub_date": "2025-02-11T12:00:00Z", "platforms": { "windows-x86_64": { - "signature": "dW50cnVzdGVkIGNvbW1lbnQ6IHNpZ25hdHVyZSBmcm9tIHRhdXJpIHNlY3JldCBrZXkKUlVRWktFU284d290TjhUR1gxYkwwbkQ1Y2xFRUhzelN1NzlIaEhUZUtydG9ZVW9pMGUxQ2c0UmJBelB4a0c4TXZrcE1VMlVUNmZUNXZUcE1zSkZBS2Q1T2VYZ3dEZ2lxOUF3PQp0cnVzdGVkIGNvbW1lbnQ6IHRpbWVzdGFtcDoxNzM5Mjk5NzIxCWZpbGU6RkY3IFVsdGltYV8xLjIuMl94NjQtc2V0dXAuZXhlCmJKTjJLUEpXTHRmUnZzUlJYMjhrL2p6YmVxTjhzdkhNd1pvZnc0RTRZdUhvaHhodzF0MGttWmFJbGRSVGwzdTNwK2dKNkpVdy9NMndsYTdzNmhjZkNBPT0K", - "url": "https://github.com/maciej-trebacz/ff7-ultima/releases/download/1.2.2/FF7.Ultima_1.2.2_x64-setup.exe" + "signature": "dW50cnVzdGVkIGNvbW1lbnQ6IHNpZ25hdHVyZSBmcm9tIHRhdXJpIHNlY3JldCBrZXkKUlVRWktFU284d290TjhJSjVzWE9iQlZkSnlBT2ZPcWVIcVZYb1k1cFlTZlBkcjJWM3dJV0RUK1lZOEl2RWlnMW5hbzUwWUh1bE9jTVJzN0VwNFZMRVl5TlIvbjRXSXJPYUFBPQp0cnVzdGVkIGNvbW1lbnQ6IHRpbWVzdGFtcDoxNzM5MzA4MDgzCWZpbGU6RkY3IFVsdGltYV8xLjIuM194NjQtc2V0dXAuZXhlCmZzWlZrVlRjdTQ3TlZhaUhmSHM2aWZUdlVTWGl1c3NIblFCQWFvaUlsWkZGdW9waWFnRC90TUQ0b2VCOFJUZzFpa3pjRVVyeHRYTDM0bmRDR29pdkNBPT0K", + "url": "https://github.com/maciej-trebacz/ff7-ultima/releases/download/1.2.3/FF7.Ultima_1.2.3_x64-setup.exe" } } } \ No newline at end of file