From 10988c4760836d68124fc253426461811a39765f Mon Sep 17 00:00:00 2001 From: Ryan Zhang Date: Fri, 8 Aug 2025 09:53:49 -0700 Subject: [PATCH 01/16] Add zone utils --- fission/src/util/DevtoolZoneUtils.ts | 113 +++++++++++++++++++++++++++ 1 file changed, 113 insertions(+) create mode 100644 fission/src/util/DevtoolZoneUtils.ts diff --git a/fission/src/util/DevtoolZoneUtils.ts b/fission/src/util/DevtoolZoneUtils.ts new file mode 100644 index 0000000000..8c5124e7bf --- /dev/null +++ b/fission/src/util/DevtoolZoneUtils.ts @@ -0,0 +1,113 @@ +import MirabufCachingService, { MiraType } from "@/mirabuf/MirabufLoader" +import FieldMiraEditor from "@/mirabuf/FieldMiraEditor" +import PreferencesSystem from "@/systems/preferences/PreferencesSystem" +import type { ScoringZonePreferences, ProtectedZonePreferences } from "@/systems/preferences/PreferenceTypes" +import World from "@/systems/World" + +export type ZoneType = "scoring" | "protected" + +/** + * Checks if a zone was originally placed by dev tools by comparing it with the cached dev tool data. + */ +export function isZoneFromDevtools( + zone: ScoringZonePreferences | ProtectedZonePreferences, + zoneType: ZoneType +): boolean { + const field = World.sceneRenderer.mirabufSceneObjects.getField() + if (!field) return false + + const parts = field.mirabufInstance.parser.assembly.data?.parts + if (!parts) return false + + const editor = new FieldMiraEditor(parts) + + if (zoneType === "scoring") { + const devtoolZones = editor.getUserData("devtool:scoring_zones") as ScoringZonePreferences[] | undefined + if (!devtoolZones) return false + + return devtoolZones.some(devZone => + devZone.name === zone.name && + devZone.alliance === zone.alliance && + devZone.parentNode === zone.parentNode && + JSON.stringify(devZone.deltaTransformation) === JSON.stringify(zone.deltaTransformation) + ) + } else { + // For protected zones, we'd need to add dev tool support first + // For now, return false as protected zones don't have dev tool support yet + return false + } +} + +/** + * Removes a zone from dev tools cache permanently. + */ +export async function removeZoneFromDevtools( + zone: ScoringZonePreferences | ProtectedZonePreferences, + zoneType: ZoneType +): Promise { + const field = World.sceneRenderer.mirabufSceneObjects.getField() + if (!field) throw new Error("No field loaded") + + const parts = field.mirabufInstance.parser.assembly.data?.parts + if (!parts) throw new Error("No field parts found") + + const editor = new FieldMiraEditor(parts) + + if (zoneType === "scoring") { + const devtoolZones = editor.getUserData("devtool:scoring_zones") as ScoringZonePreferences[] | undefined + if (!devtoolZones) return + + // Remove the zone from dev tool data + const filteredZones = devtoolZones.filter(devZone => + !(devZone.name === zone.name && + devZone.alliance === zone.alliance && + devZone.parentNode === zone.parentNode && + JSON.stringify(devZone.deltaTransformation) === JSON.stringify(zone.deltaTransformation)) + ) + + // Update the dev tool data + if (filteredZones.length === 0) { + editor.removeUserData("devtool:scoring_zones") + } else { + editor.setUserData("devtool:scoring_zones", filteredZones) + } + + // Update field preferences to match the filtered dev tool data + if (field.fieldPreferences) { + field.fieldPreferences.scoringZones = filteredZones + PreferencesSystem.savePreferences?.() + field.updateScoringZones() + } + + // Persist changes to cache + const assembly = field.mirabufInstance.parser.assembly + const cacheId = field.cacheId + if (cacheId) { + const success = await MirabufCachingService.persistDevtoolChanges(cacheId, MiraType.FIELD, assembly) + if (!success) { + throw new Error("Failed to persist changes to cache") + } + } + } else { + throw new Error("Protected zone dev tool removal not yet implemented") + } +} + +/** + * Gets all zones that exist in dev tools for a given type. + */ +export function getDevtoolZones(zoneType: ZoneType): ScoringZonePreferences[] | ProtectedZonePreferences[] | undefined { + const field = World.sceneRenderer.mirabufSceneObjects.getField() + if (!field) return undefined + + const parts = field.mirabufInstance.parser.assembly.data?.parts + if (!parts) return undefined + + const editor = new FieldMiraEditor(parts) + + if (zoneType === "scoring") { + return editor.getUserData("devtool:scoring_zones") as ScoringZonePreferences[] | undefined + } else { + return undefined + } +} \ No newline at end of file From f1c94c5db74d5d6e367c844a4184f4151a98fb42 Mon Sep 17 00:00:00 2001 From: Ryan Zhang Date: Fri, 8 Aug 2025 09:54:05 -0700 Subject: [PATCH 02/16] Add zone removal modal --- .../src/ui/modals/DevtoolZoneRemovalModal.tsx | 92 +++++++++++++++++++ .../scoring/ManageScoringZonesInterface.tsx | 55 ++++++++++- 2 files changed, 145 insertions(+), 2 deletions(-) create mode 100644 fission/src/ui/modals/DevtoolZoneRemovalModal.tsx diff --git a/fission/src/ui/modals/DevtoolZoneRemovalModal.tsx b/fission/src/ui/modals/DevtoolZoneRemovalModal.tsx new file mode 100644 index 0000000000..dcfccd56f7 --- /dev/null +++ b/fission/src/ui/modals/DevtoolZoneRemovalModal.tsx @@ -0,0 +1,92 @@ +import { Button, Dialog, DialogActions, DialogContent, DialogTitle, Stack, Typography } from "@mui/material" +import type React from "react" +import { useState } from "react" +import { globalAddToast } from "../components/GlobalUIControls" + +interface DevtoolZoneRemovalModalProps { + isOpen: boolean + onClose: () => void + zoneType: "scoring" | "protected" + zoneName: string + onTemporaryRemoval: () => void + onPermanentRemoval: () => void +} + +const DevtoolZoneRemovalModal: React.FC = ({ + isOpen, + onClose, + zoneType, + zoneName, + onTemporaryRemoval, + onPermanentRemoval, +}) => { + const [isRemoving, setIsRemoving] = useState(false) + + const handleTemporaryRemoval = () => { + onTemporaryRemoval() + onClose() + } + + const handlePermanentRemoval = async () => { + setIsRemoving(true) + try { + await onPermanentRemoval() + globalAddToast?.("info", "Zone Removed", `${zoneName} has been permanently removed from dev tools.`) + } catch (error) { + globalAddToast?.("error", "Removal Failed", "Failed to permanently remove zone from dev tools cache.") + console.error("Failed to remove zone from dev tools:", error) + } finally { + setIsRemoving(false) + onClose() + } + } + + return ( + + Remove {zoneType === "scoring" ? "Scoring" : "Protected"} Zone + + + + The {zoneType} zone "{zoneName}" was placed using dev tools and is cached. + + + Choose how you'd like to remove it: + + + + Temporary removal: Remove zone until next field reload. The zone will + reappear when you refresh or reload the field. + + + Permanent removal: Remove zone from dev tools cache. This will prevent + it from reappearing on future loads. + + + + + + + + + + + ) +} + +export default DevtoolZoneRemovalModal \ No newline at end of file diff --git a/fission/src/ui/panels/configuring/assembly-config/interfaces/scoring/ManageScoringZonesInterface.tsx b/fission/src/ui/panels/configuring/assembly-config/interfaces/scoring/ManageScoringZonesInterface.tsx index e6c22c6933..aa9b2e5c3c 100644 --- a/fission/src/ui/panels/configuring/assembly-config/interfaces/scoring/ManageScoringZonesInterface.tsx +++ b/fission/src/ui/panels/configuring/assembly-config/interfaces/scoring/ManageScoringZonesInterface.tsx @@ -9,6 +9,8 @@ import World from "@/systems/World" import Label from "@/ui/components/Label" import ScrollView from "@/ui/components/ScrollView" import { AddButton, DeleteButton, EditButton } from "@/ui/components/StyledComponents" +import DevtoolZoneRemovalModal from "@/ui/modals/DevtoolZoneRemovalModal" +import { isZoneFromDevtools, removeZoneFromDevtools } from "@/util/DevtoolZoneUtils" const saveZones = (zones: ScoringZonePreferences[] | undefined, field: MirabufSceneObject | undefined) => { if (!zones || !field) return @@ -25,9 +27,18 @@ type ScoringZoneRowProps = { save: () => void deleteZone: () => void selectZone: (zone: ScoringZonePreferences) => void + onShowConfirmation: (zone: ScoringZonePreferences) => void } -const ScoringZoneRow: React.FC = ({ zone, save, deleteZone, selectZone }) => { +const ScoringZoneRow: React.FC = ({ zone, save, deleteZone, selectZone, onShowConfirmation }) => { + const handleDeleteClick = () => { + if (isZoneFromDevtools(zone, "scoring")) { + onShowConfirmation(zone) + } else { + deleteZone() + } + } + return ( @@ -51,7 +62,7 @@ const ScoringZoneRow: React.FC = ({ zone, save, deleteZone, })} {DeleteButton(() => { - deleteZone() + handleDeleteClick() })} @@ -66,6 +77,11 @@ interface ScoringZonesProps { const ManageZonesInterface: React.FC = ({ selectedField, initialZones, selectZone }) => { const [zones, setZones] = useState(initialZones) + const [confirmationModal, setConfirmationModal] = useState<{ + isOpen: boolean + zone: ScoringZonePreferences | null + zoneIndex: number + }>({ isOpen: false, zone: null, zoneIndex: -1 }) const saveEvent = useCallback(() => { saveZones(zones, selectedField) @@ -89,6 +105,31 @@ const ManageZonesInterface: React.FC = ({ selectedField, init } }, [selectedField, zones]) + const handleShowConfirmation = (zone: ScoringZonePreferences) => { + const zoneIndex = zones.indexOf(zone) + setConfirmationModal({ isOpen: true, zone, zoneIndex }) + } + + const handleTemporaryRemoval = () => { + if (confirmationModal.zoneIndex >= 0) { + const newZones = zones.filter((_, idx) => idx !== confirmationModal.zoneIndex) + setZones(newZones) + saveZones(newZones, selectedField) + } + } + + const handlePermanentRemoval = async () => { + if (confirmationModal.zone) { + await removeZoneFromDevtools(confirmationModal.zone, "scoring") + const updatedZones = selectedField.fieldPreferences?.scoringZones ?? [] + setZones(updatedZones) + } + } + + const handleCloseConfirmation = () => { + setConfirmationModal({ isOpen: false, zone: null, zoneIndex: -1 }) + } + return ( <> {zones?.length > 0 ? ( @@ -109,6 +150,7 @@ const ManageZonesInterface: React.FC = ({ selectedField, init ) }} selectZone={selectZone} + onShowConfirmation={handleShowConfirmation} /> ))} @@ -133,6 +175,15 @@ const ManageZonesInterface: React.FC = ({ selectedField, init selectZone(newZone) })} + + ) } From 45d77da46491ba4564bc9d5b56769bf81829ca44 Mon Sep 17 00:00:00 2001 From: Ryan Zhang Date: Fri, 8 Aug 2025 10:01:44 -0700 Subject: [PATCH 03/16] chore: formatting --- .../src/ui/modals/DevtoolZoneRemovalModal.tsx | 20 +++--------- .../scoring/ManageScoringZonesInterface.tsx | 2 +- fission/src/util/DevtoolZoneUtils.ts | 32 +++++++++++-------- 3 files changed, 24 insertions(+), 30 deletions(-) diff --git a/fission/src/ui/modals/DevtoolZoneRemovalModal.tsx b/fission/src/ui/modals/DevtoolZoneRemovalModal.tsx index dcfccd56f7..841c6aed01 100644 --- a/fission/src/ui/modals/DevtoolZoneRemovalModal.tsx +++ b/fission/src/ui/modals/DevtoolZoneRemovalModal.tsx @@ -58,8 +58,8 @@ const DevtoolZoneRemovalModal: React.FC = ({ reappear when you refresh or reload the field. - Permanent removal: Remove zone from dev tools cache. This will prevent - it from reappearing on future loads. + Permanent removal: Remove zone from dev tools cache. This will prevent it + from reappearing on future loads. @@ -68,20 +68,10 @@ const DevtoolZoneRemovalModal: React.FC = ({ - - @@ -89,4 +79,4 @@ const DevtoolZoneRemovalModal: React.FC = ({ ) } -export default DevtoolZoneRemovalModal \ No newline at end of file +export default DevtoolZoneRemovalModal diff --git a/fission/src/ui/panels/configuring/assembly-config/interfaces/scoring/ManageScoringZonesInterface.tsx b/fission/src/ui/panels/configuring/assembly-config/interfaces/scoring/ManageScoringZonesInterface.tsx index aa9b2e5c3c..d06481d925 100644 --- a/fission/src/ui/panels/configuring/assembly-config/interfaces/scoring/ManageScoringZonesInterface.tsx +++ b/fission/src/ui/panels/configuring/assembly-config/interfaces/scoring/ManageScoringZonesInterface.tsx @@ -175,7 +175,7 @@ const ManageZonesInterface: React.FC = ({ selectedField, init selectZone(newZone) })} - + - devZone.name === zone.name && - devZone.alliance === zone.alliance && - devZone.parentNode === zone.parentNode && - JSON.stringify(devZone.deltaTransformation) === JSON.stringify(zone.deltaTransformation) + return devtoolZones.some( + devZone => + devZone.name === zone.name && + devZone.alliance === zone.alliance && + devZone.parentNode === zone.parentNode && + JSON.stringify(devZone.deltaTransformation) === JSON.stringify(zone.deltaTransformation) ) } else { // For protected zones, we'd need to add dev tool support first @@ -52,17 +53,20 @@ export async function removeZoneFromDevtools( if (!parts) throw new Error("No field parts found") const editor = new FieldMiraEditor(parts) - + if (zoneType === "scoring") { const devtoolZones = editor.getUserData("devtool:scoring_zones") as ScoringZonePreferences[] | undefined if (!devtoolZones) return // Remove the zone from dev tool data - const filteredZones = devtoolZones.filter(devZone => - !(devZone.name === zone.name && - devZone.alliance === zone.alliance && - devZone.parentNode === zone.parentNode && - JSON.stringify(devZone.deltaTransformation) === JSON.stringify(zone.deltaTransformation)) + const filteredZones = devtoolZones.filter( + devZone => + !( + devZone.name === zone.name && + devZone.alliance === zone.alliance && + devZone.parentNode === zone.parentNode && + JSON.stringify(devZone.deltaTransformation) === JSON.stringify(zone.deltaTransformation) + ) ) // Update the dev tool data @@ -104,10 +108,10 @@ export function getDevtoolZones(zoneType: ZoneType): ScoringZonePreferences[] | if (!parts) return undefined const editor = new FieldMiraEditor(parts) - + if (zoneType === "scoring") { return editor.getUserData("devtool:scoring_zones") as ScoringZonePreferences[] | undefined } else { return undefined } -} \ No newline at end of file +} From 2d92ac96c9f3a91d7937e79c99d05703f93e1f3b Mon Sep 17 00:00:00 2001 From: Ryan Zhang Date: Mon, 11 Aug 2025 11:20:27 -0700 Subject: [PATCH 04/16] Update toasts and descs --- .../src/ui/modals/DevtoolZoneRemovalModal.tsx | 10 +++++----- fission/src/util/DevtoolZoneUtils.ts | 18 +++++++++--------- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/fission/src/ui/modals/DevtoolZoneRemovalModal.tsx b/fission/src/ui/modals/DevtoolZoneRemovalModal.tsx index 841c6aed01..df1c3d6372 100644 --- a/fission/src/ui/modals/DevtoolZoneRemovalModal.tsx +++ b/fission/src/ui/modals/DevtoolZoneRemovalModal.tsx @@ -31,10 +31,10 @@ const DevtoolZoneRemovalModal: React.FC = ({ setIsRemoving(true) try { await onPermanentRemoval() - globalAddToast?.("info", "Zone Removed", `${zoneName} has been permanently removed from dev tools.`) + globalAddToast?.("info", "Zone Removed", `${zoneName} has been permanently removed from the field file.`) } catch (error) { - globalAddToast?.("error", "Removal Failed", "Failed to permanently remove zone from dev tools cache.") - console.error("Failed to remove zone from dev tools:", error) + globalAddToast?.("error", "Removal Failed", "Failed to permanently remove zone from the field file cache.") + console.error("Failed to remove zone from field file:", error) } finally { setIsRemoving(false) onClose() @@ -47,7 +47,7 @@ const DevtoolZoneRemovalModal: React.FC = ({ - The {zoneType} zone "{zoneName}" was placed using dev tools and is cached. + The {zoneType} zone "{zoneName}" was defined in the field file and is cached. Choose how you'd like to remove it: @@ -58,7 +58,7 @@ const DevtoolZoneRemovalModal: React.FC = ({ reappear when you refresh or reload the field. - Permanent removal: Remove zone from dev tools cache. This will prevent it + Permanent removal: Remove zone from the field file cache. This will prevent it from reappearing on future loads. diff --git a/fission/src/util/DevtoolZoneUtils.ts b/fission/src/util/DevtoolZoneUtils.ts index 77ee139162..99f2eec616 100644 --- a/fission/src/util/DevtoolZoneUtils.ts +++ b/fission/src/util/DevtoolZoneUtils.ts @@ -7,7 +7,7 @@ import World from "@/systems/World" export type ZoneType = "scoring" | "protected" /** - * Checks if a zone was originally placed by dev tools by comparing it with the cached dev tool data. + * Checks if a zone was originally defined in the field file by comparing it with the cached field data. */ export function isZoneFromDevtools( zone: ScoringZonePreferences | ProtectedZonePreferences, @@ -33,14 +33,14 @@ export function isZoneFromDevtools( JSON.stringify(devZone.deltaTransformation) === JSON.stringify(zone.deltaTransformation) ) } else { - // For protected zones, we'd need to add dev tool support first - // For now, return false as protected zones don't have dev tool support yet + // For protected zones, we'd need to add field file support first + // For now, return false as protected zones don't have field file support yet return false } } /** - * Removes a zone from dev tools cache permanently. + * Removes a zone from the field file cache permanently. */ export async function removeZoneFromDevtools( zone: ScoringZonePreferences | ProtectedZonePreferences, @@ -58,7 +58,7 @@ export async function removeZoneFromDevtools( const devtoolZones = editor.getUserData("devtool:scoring_zones") as ScoringZonePreferences[] | undefined if (!devtoolZones) return - // Remove the zone from dev tool data + // Remove the zone from field file data const filteredZones = devtoolZones.filter( devZone => !( @@ -69,14 +69,14 @@ export async function removeZoneFromDevtools( ) ) - // Update the dev tool data + // Update the field file data if (filteredZones.length === 0) { editor.removeUserData("devtool:scoring_zones") } else { editor.setUserData("devtool:scoring_zones", filteredZones) } - // Update field preferences to match the filtered dev tool data + // Update field preferences to match the filtered field file data if (field.fieldPreferences) { field.fieldPreferences.scoringZones = filteredZones PreferencesSystem.savePreferences?.() @@ -93,12 +93,12 @@ export async function removeZoneFromDevtools( } } } else { - throw new Error("Protected zone dev tool removal not yet implemented") + throw new Error("Protected zone field file removal not yet implemented") } } /** - * Gets all zones that exist in dev tools for a given type. + * Gets all zones that exist in the field file for a given type. */ export function getDevtoolZones(zoneType: ZoneType): ScoringZonePreferences[] | ProtectedZonePreferences[] | undefined { const field = World.sceneRenderer.mirabufSceneObjects.getField() From 10c8f1a63dabc4db7edad697d98f0a7f9c327b29 Mon Sep 17 00:00:00 2001 From: Ryan Zhang Date: Wed, 13 Aug 2025 15:51:25 -0700 Subject: [PATCH 05/16] Add permanent modification --- .../src/ui/modals/DevtoolZoneRemovalModal.tsx | 27 +++-- .../assembly-config/ConfigurePanel.tsx | 4 +- .../ConfigureScoringZonesInterface.tsx | 6 +- .../scoring/ScoringZoneConfigInterface.tsx | 111 ++++++++++++++++-- fission/src/util/DevtoolZoneUtils.ts | 54 +++++++++ 5 files changed, 176 insertions(+), 26 deletions(-) diff --git a/fission/src/ui/modals/DevtoolZoneRemovalModal.tsx b/fission/src/ui/modals/DevtoolZoneRemovalModal.tsx index df1c3d6372..cf2eb473a4 100644 --- a/fission/src/ui/modals/DevtoolZoneRemovalModal.tsx +++ b/fission/src/ui/modals/DevtoolZoneRemovalModal.tsx @@ -10,6 +10,7 @@ interface DevtoolZoneRemovalModalProps { zoneName: string onTemporaryRemoval: () => void onPermanentRemoval: () => void + actionType?: "removal" | "modification" } const DevtoolZoneRemovalModal: React.FC = ({ @@ -19,6 +20,7 @@ const DevtoolZoneRemovalModal: React.FC = ({ zoneName, onTemporaryRemoval, onPermanentRemoval, + actionType = "removal", }) => { const [isRemoving, setIsRemoving] = useState(false) @@ -31,10 +33,13 @@ const DevtoolZoneRemovalModal: React.FC = ({ setIsRemoving(true) try { await onPermanentRemoval() - globalAddToast?.("info", "Zone Removed", `${zoneName} has been permanently removed from the field file.`) + const actionText = actionType === "modification" ? "modified" : "removed" + const actionCapitalized = actionType === "modification" ? "Modified" : "Removed" + globalAddToast?.("info", `Zone ${actionCapitalized}`, `${zoneName} has been permanently ${actionText} in the field file.`) } catch (error) { - globalAddToast?.("error", "Removal Failed", "Failed to permanently remove zone from the field file cache.") - console.error("Failed to remove zone from field file:", error) + const actionText = actionType === "modification" ? "modify" : "remove" + globalAddToast?.("error", `${actionType === "modification" ? "Modification" : "Removal"} Failed`, `Failed to permanently ${actionText} zone in the field file cache.`) + console.error(`Failed to ${actionText} zone in field file:`, error) } finally { setIsRemoving(false) onClose() @@ -43,23 +48,21 @@ const DevtoolZoneRemovalModal: React.FC = ({ return ( - Remove {zoneType === "scoring" ? "Scoring" : "Protected"} Zone + {actionType === "modification" ? "Modify" : "Remove"} {zoneType === "scoring" ? "Scoring" : "Protected"} Zone The {zoneType} zone "{zoneName}" was defined in the field file and is cached. - Choose how you'd like to remove it: + Choose how you'd like to {actionType === "modification" ? "save your modifications" : "remove it"}: - Temporary removal: Remove zone until next field reload. The zone will - reappear when you refresh or reload the field. + Temporary {actionType === "modification" ? "modification" : "removal"}: {actionType === "modification" ? "Save changes until next field reload. Original zone will reappear when you refresh or reload the field." : "Remove zone until next field reload. The zone will reappear when you refresh or reload the field."} - Permanent removal: Remove zone from the field file cache. This will prevent it - from reappearing on future loads. + Permanent {actionType === "modification" ? "modification" : "removal"}: {actionType === "modification" ? "Save changes to the field file cache. This will persist your modifications on future loads." : "Remove zone from the field file cache. This will prevent it from reappearing on future loads."} @@ -69,10 +72,10 @@ const DevtoolZoneRemovalModal: React.FC = ({ Cancel - diff --git a/fission/src/ui/panels/configuring/assembly-config/ConfigurePanel.tsx b/fission/src/ui/panels/configuring/assembly-config/ConfigurePanel.tsx index ea2222bedc..3629421942 100644 --- a/fission/src/ui/panels/configuring/assembly-config/ConfigurePanel.tsx +++ b/fission/src/ui/panels/configuring/assembly-config/ConfigurePanel.tsx @@ -14,7 +14,7 @@ import World from "@/systems/World" import Label from "@/ui/components/Label" import type { PanelImplProps } from "@/ui/components/Panel" import TransformGizmoControl from "@/ui/components/TransformGizmoControl" -import { CloseType, type UIScreen, useUIContext } from "@/ui/helpers/UIProviderHelpers" +import { CloseType, type Panel, type UIScreen, useUIContext } from "@/ui/helpers/UIProviderHelpers" import ChooseInputSchemePanel from "../ChooseInputSchemePanel" import { CONFIG_OPTS, ConfigMode, type ConfigurationType } from "./ConfigTypes" import AssemblySelection, { type AssemblySelectionOption } from "./configure/AssemblySelection" @@ -79,7 +79,7 @@ const ConfigInterface: React.FCERROR: Field does not contain scoring zone configuration! } - return + return } selectedField={assembly} initialZones={zones} /> } case ConfigMode.PROTECTED_ZONES: { const zones = assembly.fieldPreferences?.protectedZones ?? [] diff --git a/fission/src/ui/panels/configuring/assembly-config/interfaces/scoring/ConfigureScoringZonesInterface.tsx b/fission/src/ui/panels/configuring/assembly-config/interfaces/scoring/ConfigureScoringZonesInterface.tsx index 4919e2bc65..3c4ee82689 100644 --- a/fission/src/ui/panels/configuring/assembly-config/interfaces/scoring/ConfigureScoringZonesInterface.tsx +++ b/fission/src/ui/panels/configuring/assembly-config/interfaces/scoring/ConfigureScoringZonesInterface.tsx @@ -20,12 +20,15 @@ const saveZones = (zones: ScoringZonePreferences[] | undefined, field: MirabufSc field.updateScoringZones() } +import type { Panel } from "@/ui/helpers/UIProviderHelpers" + interface ConfigureZonesProps { selectedField: MirabufSceneObject initialZones: ScoringZonePreferences[] + panel?: Panel } -const ConfigureScoringZonesInterface: React.FC = ({ selectedField, initialZones }) => { +const ConfigureScoringZonesInterface: React.FC = ({ selectedField, initialZones, panel }) => { const [selectedZone, setSelectedZone] = useState(undefined) return ( @@ -65,6 +68,7 @@ const ConfigureScoringZonesInterface: React.FC = ({ selecte saveAllZones={() => { saveZones(selectedField.fieldPreferences?.scoringZones, selectedField) }} + panel={panel} /> )} diff --git a/fission/src/ui/panels/configuring/assembly-config/interfaces/scoring/ScoringZoneConfigInterface.tsx b/fission/src/ui/panels/configuring/assembly-config/interfaces/scoring/ScoringZoneConfigInterface.tsx index ae57f6e878..c87761fcff 100644 --- a/fission/src/ui/panels/configuring/assembly-config/interfaces/scoring/ScoringZoneConfigInterface.tsx +++ b/fission/src/ui/panels/configuring/assembly-config/interfaces/scoring/ScoringZoneConfigInterface.tsx @@ -1,5 +1,5 @@ import type Jolt from "@azaleacolburn/jolt-physics" -import { Button, TextField } from "@mui/material" +import { Button, Stack, TextField } from "@mui/material" import { useCallback, useEffect, useMemo, useRef, useState } from "react" import * as THREE from "three" import { ConfigurationSavedEvent } from "@/events/ConfigurationSavedEvent" @@ -11,15 +11,20 @@ import PreferencesSystem from "@/systems/preferences/PreferencesSystem" import type { Alliance, ScoringZonePreferences } from "@/systems/preferences/PreferenceTypes" import type GizmoSceneObject from "@/systems/scene/GizmoSceneObject" import World from "@/systems/World" + import Checkbox from "@/ui/components/Checkbox" import SelectButton from "@/ui/components/SelectButton" import TransformGizmoControl from "@/ui/components/TransformGizmoControl" +import DevtoolZoneRemovalModal from "@/ui/modals/DevtoolZoneRemovalModal" +import { isZoneFromDevtools, modifyZoneInDevtools } from "@/util/DevtoolZoneUtils" import { convertArrayToThreeMatrix4, convertJoltMat44ToThreeMatrix4, convertThreeMatrix4ToArray, } from "@/util/TypeConversions" import { deltaFieldTransformsPhysicalProp as deltaFieldTransformsVisualProperties } from "@/util/threejs/MeshCreation" +import type { Panel } from "@/ui/helpers/UIProviderHelpers" +import { CloseType, useUIContext } from "@/ui/helpers/UIProviderHelpers" /** * Saves ejector configuration to selected field. @@ -101,9 +106,10 @@ interface ZoneConfigProps { selectedField: MirabufSceneObject selectedZone: ScoringZonePreferences saveAllZones: () => void + panel?: Panel } -const ZoneConfigInterface: React.FC = ({ selectedField, selectedZone, saveAllZones }) => { +const ZoneConfigInterface: React.FC = ({ selectedField, selectedZone, saveAllZones, panel }) => { //Official FIRST hex // TODO: Do we want to eventually make these editable? const redMaterial = useMemo(() => { @@ -130,10 +136,23 @@ const ZoneConfigInterface: React.FC = ({ selectedField, selecte const [points, setPoints] = useState(selectedZone.points) const [destroy] = useState(selectedZone.destroyGamepiece) const [persistent, setPersistent] = useState(selectedZone.persistentPoints) + const [confirmationModal, setConfirmationModal] = useState<{ + isOpen: boolean + pendingSave: boolean + }>({ isOpen: false, pendingSave: false }) const gizmoRef = useRef(undefined) + const originalZoneRef = useRef(structuredClone(selectedZone)) + const { configureScreen, closePanel } = useUIContext() + + // Hide the panel's default footer buttons when this interface is active + useEffect(() => { + if (panel) { + configureScreen(panel, { hideAccept: true, hideCancel: true }, {}) + } + }, [panel, configureScreen]) - const saveEvent = useCallback(() => { + const handleSave = useCallback(() => { if (gizmoRef.current && selectedField) { save( selectedField, @@ -150,14 +169,6 @@ const ZoneConfigInterface: React.FC = ({ selectedField, selecte } }, [selectedField, selectedZone, name, alliance, points, destroy, persistent, selectedNode, saveAllZones]) - useEffect(() => { - ConfigurationSavedEvent.listen(saveEvent) - - return () => { - ConfigurationSavedEvent.removeListener(saveEvent) - } - }, [saveEvent]) - /** Holds a pause for the duration of the interface component */ useEffect(() => { World.physicsSystem.holdPause(PAUSE_REF_ASSEMBLY_CONFIG) @@ -245,6 +256,47 @@ const ZoneConfigInterface: React.FC = ({ selectedField, selecte [selectedField] ) + const handleTemporaryModification = () => { + // For temporary modification, just save the changes to the current session + // without persisting to the field file cache + handleSave() + + // Save all zones to ensure the changes are persisted to preferences + saveAllZones() + + setConfirmationModal({ isOpen: false, pendingSave: false }) + if (panel) closePanel(panel.id, CloseType.Accept) + } + + const handlePermanentModification = async () => { + try { + const modifiedZone: ScoringZonePreferences = { + name, + alliance, + parentNode: selectedNode, + points, + destroyGamepiece: destroy, + persistentPoints: persistent, + deltaTransformation: selectedZone.deltaTransformation + } + + handleSave() + modifiedZone.deltaTransformation = selectedZone.deltaTransformation + + await modifyZoneInDevtools(originalZoneRef.current, modifiedZone, "scoring") + + } catch (error) { + console.error("Failed to permanently modify zone:", error) + } finally { + setConfirmationModal({ isOpen: false, pendingSave: false }) + if (panel) closePanel(panel.id, CloseType.Accept) + } + } + + const handleCloseConfirmation = () => { + setConfirmationModal({ isOpen: false, pendingSave: false }) + } + return (
{/** Set the zone name */} @@ -294,6 +346,43 @@ const ZoneConfigInterface: React.FC = ({ selectedField, selecte {/** Switch between transform control modes */} {gizmoComponent} + + + + {/** Custom Save/Cancel buttons that replace the panel's default buttons */} + + + +
) } diff --git a/fission/src/util/DevtoolZoneUtils.ts b/fission/src/util/DevtoolZoneUtils.ts index 99f2eec616..ce50afaeeb 100644 --- a/fission/src/util/DevtoolZoneUtils.ts +++ b/fission/src/util/DevtoolZoneUtils.ts @@ -97,6 +97,60 @@ export async function removeZoneFromDevtools( } } +/** + * Modifies a zone in the field file cache permanently by replacing it with updated data. + */ +export async function modifyZoneInDevtools( + originalZone: ScoringZonePreferences | ProtectedZonePreferences, + modifiedZone: ScoringZonePreferences | ProtectedZonePreferences, + zoneType: ZoneType +): Promise { + const field = World.sceneRenderer.mirabufSceneObjects.getField() + if (!field) throw new Error("No field loaded") + + const parts = field.mirabufInstance.parser.assembly.data?.parts + if (!parts) throw new Error("No field parts found") + + const editor = new FieldMiraEditor(parts) + + if (zoneType === "scoring") { + const devtoolZones = editor.getUserData("devtool:scoring_zones") as ScoringZonePreferences[] | undefined + if (!devtoolZones) return + + // Find and replace the zone in field file data + const updatedZones = devtoolZones.map(devZone => { + if ( + devZone.name === originalZone.name && + devZone.alliance === originalZone.alliance && + devZone.parentNode === originalZone.parentNode && + JSON.stringify(devZone.deltaTransformation) === JSON.stringify(originalZone.deltaTransformation) + ) { + return modifiedZone as ScoringZonePreferences + } + return devZone + }) + + editor.setUserData("devtool:scoring_zones", updatedZones) + + if (field.fieldPreferences) { + field.fieldPreferences.scoringZones = updatedZones + PreferencesSystem.savePreferences?.() + field.updateScoringZones() + } + + const assembly = field.mirabufInstance.parser.assembly + const cacheId = field.cacheId + if (cacheId) { + const success = await MirabufCachingService.persistDevtoolChanges(cacheId, MiraType.FIELD, assembly) + if (!success) { + throw new Error("Failed to persist changes to cache") + } + } + } else { + throw new Error("Protected zone field file modification not yet implemented") + } +} + /** * Gets all zones that exist in the field file for a given type. */ From cde88abdf83f7809398eabc9da3ac0c258b118f9 Mon Sep 17 00:00:00 2001 From: Ryan Zhang Date: Thu, 14 Aug 2025 13:29:10 -0700 Subject: [PATCH 06/16] Update temporary modification --- .../scoring/ScoringZoneConfigInterface.tsx | 45 ++++++++++++++++--- 1 file changed, 39 insertions(+), 6 deletions(-) diff --git a/fission/src/ui/panels/configuring/assembly-config/interfaces/scoring/ScoringZoneConfigInterface.tsx b/fission/src/ui/panels/configuring/assembly-config/interfaces/scoring/ScoringZoneConfigInterface.tsx index c87761fcff..1010fd343c 100644 --- a/fission/src/ui/panels/configuring/assembly-config/interfaces/scoring/ScoringZoneConfigInterface.tsx +++ b/fission/src/ui/panels/configuring/assembly-config/interfaces/scoring/ScoringZoneConfigInterface.tsx @@ -257,12 +257,45 @@ const ZoneConfigInterface: React.FC = ({ selectedField, selecte ) const handleTemporaryModification = () => { - // For temporary modification, just save the changes to the current session - // without persisting to the field file cache - handleSave() - - // Save all zones to ensure the changes are persisted to preferences - saveAllZones() + if (gizmoRef.current && selectedField) { + save( + selectedField, + selectedZone, + name, + alliance, + points, + destroy, + persistent, + gizmoRef.current, + selectedNode + ) + + const fieldZones = selectedField.fieldPreferences?.scoringZones + if (fieldZones) { + const zoneIndex = fieldZones.findIndex(z => + z === selectedZone || + (z.name === originalZoneRef.current.name && + z.alliance === originalZoneRef.current.alliance && + z.parentNode === originalZoneRef.current.parentNode && + JSON.stringify(z.deltaTransformation) === JSON.stringify(originalZoneRef.current.deltaTransformation)) + ) + + if (zoneIndex >= 0) { + fieldZones[zoneIndex] = { + name, + alliance, + parentNode: selectedNode, + points, + destroyGamepiece: destroy, + persistentPoints: persistent, + deltaTransformation: selectedZone.deltaTransformation + } + } + } + + PreferencesSystem.savePreferences() + selectedField.updateScoringZones() + } setConfirmationModal({ isOpen: false, pendingSave: false }) if (panel) closePanel(panel.id, CloseType.Accept) From f9d09008ae0a033b20e0b0e4a8326625ac92a3f3 Mon Sep 17 00:00:00 2001 From: Ryan Zhang Date: Thu, 14 Aug 2025 13:32:05 -0700 Subject: [PATCH 07/16] chore: formatting --- .../src/ui/modals/DevtoolZoneRemovalModal.tsx | 41 +++++++++++++++---- .../assembly-config/ConfigurePanel.tsx | 8 +++- .../scoring/ScoringZoneConfigInterface.tsx | 33 +++++++-------- fission/src/util/DevtoolZoneUtils.ts | 4 +- 4 files changed, 59 insertions(+), 27 deletions(-) diff --git a/fission/src/ui/modals/DevtoolZoneRemovalModal.tsx b/fission/src/ui/modals/DevtoolZoneRemovalModal.tsx index cf2eb473a4..4009beef6b 100644 --- a/fission/src/ui/modals/DevtoolZoneRemovalModal.tsx +++ b/fission/src/ui/modals/DevtoolZoneRemovalModal.tsx @@ -35,10 +35,18 @@ const DevtoolZoneRemovalModal: React.FC = ({ await onPermanentRemoval() const actionText = actionType === "modification" ? "modified" : "removed" const actionCapitalized = actionType === "modification" ? "Modified" : "Removed" - globalAddToast?.("info", `Zone ${actionCapitalized}`, `${zoneName} has been permanently ${actionText} in the field file.`) + globalAddToast?.( + "info", + `Zone ${actionCapitalized}`, + `${zoneName} has been permanently ${actionText} in the field file.` + ) } catch (error) { const actionText = actionType === "modification" ? "modify" : "remove" - globalAddToast?.("error", `${actionType === "modification" ? "Modification" : "Removal"} Failed`, `Failed to permanently ${actionText} zone in the field file cache.`) + globalAddToast?.( + "error", + `${actionType === "modification" ? "Modification" : "Removal"} Failed`, + `Failed to permanently ${actionText} zone in the field file cache.` + ) console.error(`Failed to ${actionText} zone in field file:`, error) } finally { setIsRemoving(false) @@ -48,21 +56,31 @@ const DevtoolZoneRemovalModal: React.FC = ({ return ( - {actionType === "modification" ? "Modify" : "Remove"} {zoneType === "scoring" ? "Scoring" : "Protected"} Zone + + {actionType === "modification" ? "Modify" : "Remove"} {zoneType === "scoring" ? "Scoring" : "Protected"}{" "} + Zone + The {zoneType} zone "{zoneName}" was defined in the field file and is cached. - Choose how you'd like to {actionType === "modification" ? "save your modifications" : "remove it"}: + Choose how you'd like to{" "} + {actionType === "modification" ? "save your modifications" : "remove it"}: - Temporary {actionType === "modification" ? "modification" : "removal"}: {actionType === "modification" ? "Save changes until next field reload. Original zone will reappear when you refresh or reload the field." : "Remove zone until next field reload. The zone will reappear when you refresh or reload the field."} + Temporary {actionType === "modification" ? "modification" : "removal"}:{" "} + {actionType === "modification" + ? "Save changes until next field reload. Original zone will reappear when you refresh or reload the field." + : "Remove zone until next field reload. The zone will reappear when you refresh or reload the field."} - Permanent {actionType === "modification" ? "modification" : "removal"}: {actionType === "modification" ? "Save changes to the field file cache. This will persist your modifications on future loads." : "Remove zone from the field file cache. This will prevent it from reappearing on future loads."} + Permanent {actionType === "modification" ? "modification" : "removal"}:{" "} + {actionType === "modification" + ? "Save changes to the field file cache. This will persist your modifications on future loads." + : "Remove zone from the field file cache. This will prevent it from reappearing on future loads."} @@ -74,8 +92,15 @@ const DevtoolZoneRemovalModal: React.FC = ({ - diff --git a/fission/src/ui/panels/configuring/assembly-config/ConfigurePanel.tsx b/fission/src/ui/panels/configuring/assembly-config/ConfigurePanel.tsx index 3629421942..8b218c8dfd 100644 --- a/fission/src/ui/panels/configuring/assembly-config/ConfigurePanel.tsx +++ b/fission/src/ui/panels/configuring/assembly-config/ConfigurePanel.tsx @@ -79,7 +79,13 @@ const ConfigInterface: React.FCERROR: Field does not contain scoring zone configuration! } - return } selectedField={assembly} initialZones={zones} /> + return ( + } + selectedField={assembly} + initialZones={zones} + /> + ) } case ConfigMode.PROTECTED_ZONES: { const zones = assembly.fieldPreferences?.protectedZones ?? [] diff --git a/fission/src/ui/panels/configuring/assembly-config/interfaces/scoring/ScoringZoneConfigInterface.tsx b/fission/src/ui/panels/configuring/assembly-config/interfaces/scoring/ScoringZoneConfigInterface.tsx index 1010fd343c..76b57a073f 100644 --- a/fission/src/ui/panels/configuring/assembly-config/interfaces/scoring/ScoringZoneConfigInterface.tsx +++ b/fission/src/ui/panels/configuring/assembly-config/interfaces/scoring/ScoringZoneConfigInterface.tsx @@ -15,6 +15,8 @@ import World from "@/systems/World" import Checkbox from "@/ui/components/Checkbox" import SelectButton from "@/ui/components/SelectButton" import TransformGizmoControl from "@/ui/components/TransformGizmoControl" +import type { Panel } from "@/ui/helpers/UIProviderHelpers" +import { CloseType, useUIContext } from "@/ui/helpers/UIProviderHelpers" import DevtoolZoneRemovalModal from "@/ui/modals/DevtoolZoneRemovalModal" import { isZoneFromDevtools, modifyZoneInDevtools } from "@/util/DevtoolZoneUtils" import { @@ -23,8 +25,6 @@ import { convertThreeMatrix4ToArray, } from "@/util/TypeConversions" import { deltaFieldTransformsPhysicalProp as deltaFieldTransformsVisualProperties } from "@/util/threejs/MeshCreation" -import type { Panel } from "@/ui/helpers/UIProviderHelpers" -import { CloseType, useUIContext } from "@/ui/helpers/UIProviderHelpers" /** * Saves ejector configuration to selected field. @@ -269,17 +269,19 @@ const ZoneConfigInterface: React.FC = ({ selectedField, selecte gizmoRef.current, selectedNode ) - + const fieldZones = selectedField.fieldPreferences?.scoringZones if (fieldZones) { - const zoneIndex = fieldZones.findIndex(z => - z === selectedZone || - (z.name === originalZoneRef.current.name && - z.alliance === originalZoneRef.current.alliance && - z.parentNode === originalZoneRef.current.parentNode && - JSON.stringify(z.deltaTransformation) === JSON.stringify(originalZoneRef.current.deltaTransformation)) + const zoneIndex = fieldZones.findIndex( + z => + z === selectedZone || + (z.name === originalZoneRef.current.name && + z.alliance === originalZoneRef.current.alliance && + z.parentNode === originalZoneRef.current.parentNode && + JSON.stringify(z.deltaTransformation) === + JSON.stringify(originalZoneRef.current.deltaTransformation)) ) - + if (zoneIndex >= 0) { fieldZones[zoneIndex] = { name, @@ -288,15 +290,15 @@ const ZoneConfigInterface: React.FC = ({ selectedField, selecte points, destroyGamepiece: destroy, persistentPoints: persistent, - deltaTransformation: selectedZone.deltaTransformation + deltaTransformation: selectedZone.deltaTransformation, } } } - + PreferencesSystem.savePreferences() selectedField.updateScoringZones() } - + setConfirmationModal({ isOpen: false, pendingSave: false }) if (panel) closePanel(panel.id, CloseType.Accept) } @@ -310,14 +312,13 @@ const ZoneConfigInterface: React.FC = ({ selectedField, selecte points, destroyGamepiece: destroy, persistentPoints: persistent, - deltaTransformation: selectedZone.deltaTransformation + deltaTransformation: selectedZone.deltaTransformation, } - + handleSave() modifiedZone.deltaTransformation = selectedZone.deltaTransformation await modifyZoneInDevtools(originalZoneRef.current, modifiedZone, "scoring") - } catch (error) { console.error("Failed to permanently modify zone:", error) } finally { diff --git a/fission/src/util/DevtoolZoneUtils.ts b/fission/src/util/DevtoolZoneUtils.ts index ce50afaeeb..88ac02ea60 100644 --- a/fission/src/util/DevtoolZoneUtils.ts +++ b/fission/src/util/DevtoolZoneUtils.ts @@ -1,7 +1,7 @@ -import MirabufCachingService, { MiraType } from "@/mirabuf/MirabufLoader" import FieldMiraEditor from "@/mirabuf/FieldMiraEditor" +import MirabufCachingService, { MiraType } from "@/mirabuf/MirabufLoader" import PreferencesSystem from "@/systems/preferences/PreferencesSystem" -import type { ScoringZonePreferences, ProtectedZonePreferences } from "@/systems/preferences/PreferenceTypes" +import type { ProtectedZonePreferences, ScoringZonePreferences } from "@/systems/preferences/PreferenceTypes" import World from "@/systems/World" export type ZoneType = "scoring" | "protected" From ddff42ff4085bb3cd1a6dd1a48f17ea8f53d5b25 Mon Sep 17 00:00:00 2001 From: Ryan Zhang Date: Thu, 14 Aug 2025 14:02:08 -0700 Subject: [PATCH 08/16] cleanup --- .../interfaces/scoring/ScoringZoneConfigInterface.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/fission/src/ui/panels/configuring/assembly-config/interfaces/scoring/ScoringZoneConfigInterface.tsx b/fission/src/ui/panels/configuring/assembly-config/interfaces/scoring/ScoringZoneConfigInterface.tsx index 76b57a073f..5234b49c30 100644 --- a/fission/src/ui/panels/configuring/assembly-config/interfaces/scoring/ScoringZoneConfigInterface.tsx +++ b/fission/src/ui/panels/configuring/assembly-config/interfaces/scoring/ScoringZoneConfigInterface.tsx @@ -2,7 +2,6 @@ import type Jolt from "@azaleacolburn/jolt-physics" import { Button, Stack, TextField } from "@mui/material" import { useCallback, useEffect, useMemo, useRef, useState } from "react" import * as THREE from "three" -import { ConfigurationSavedEvent } from "@/events/ConfigurationSavedEvent" import type { RigidNodeId } from "@/mirabuf/MirabufParser" import type MirabufSceneObject from "@/mirabuf/MirabufSceneObject" import type { RigidNodeAssociate } from "@/mirabuf/MirabufSceneObject" From 74d249f4b2701d3f2d3054b6c8bc1b98ecf0b04d Mon Sep 17 00:00:00 2001 From: Ryan Zhang Date: Mon, 18 Aug 2025 14:53:52 -0700 Subject: [PATCH 09/16] Update removal to modification --- .../modals/DevtoolZoneModificationModal.tsx | 95 +++++++++++++++ .../src/ui/modals/DevtoolZoneRemovalModal.tsx | 110 ------------------ 2 files changed, 95 insertions(+), 110 deletions(-) create mode 100644 fission/src/ui/modals/DevtoolZoneModificationModal.tsx delete mode 100644 fission/src/ui/modals/DevtoolZoneRemovalModal.tsx diff --git a/fission/src/ui/modals/DevtoolZoneModificationModal.tsx b/fission/src/ui/modals/DevtoolZoneModificationModal.tsx new file mode 100644 index 0000000000..76b3110193 --- /dev/null +++ b/fission/src/ui/modals/DevtoolZoneModificationModal.tsx @@ -0,0 +1,95 @@ +import { Button, Dialog, DialogActions, DialogContent, DialogTitle, Stack, Typography } from "@mui/material" +import type React from "react" +import { useState } from "react" +import { globalAddToast } from "../components/GlobalUIControls" + +interface DevtoolZoneModificationModalProps { + isOpen: boolean + onClose: () => void + zoneType: "scoring" | "protected" + zoneName: string + onTemporaryModification: () => void + onPermanentModification: () => void +} + +const DevtoolZoneModificationModal: React.FC = ({ + isOpen, + onClose, + zoneType, + zoneName, + onTemporaryModification, + onPermanentModification, +}) => { + const [isModifying, setIsModifying] = useState(false) + + const handleTemporaryModification = () => { + onTemporaryModification() + onClose() + } + + const handlePermanentModification = async () => { + setIsModifying(true) + try { + await onPermanentModification() + globalAddToast?.( + "info", + "Zone Modified", + `${zoneName} has been permanently modified in the field file.` + ) + } catch (error) { + globalAddToast?.( + "error", + "Modification Failed", + "Failed to permanently modify zone in the field file cache." + ) + console.error("Failed to modify zone in field file:", error) + } finally { + setIsModifying(false) + onClose() + } + } + + return ( + + + Modify {zoneType === "scoring" ? "Scoring" : "Protected"} Zone + + + + + The {zoneType} zone "{zoneName}" was defined in the field file and is cached. + + + Choose how you'd like to save your modifications: + + + + Temporary modification: Save changes until next field reload. Original zone will reappear when you refresh the page. + + + Permanent modification: Save changes to the local asset file. This will persist your modifications until you remove it from the cache. + + + + + + + + + + + ) +} + +export default DevtoolZoneModificationModal diff --git a/fission/src/ui/modals/DevtoolZoneRemovalModal.tsx b/fission/src/ui/modals/DevtoolZoneRemovalModal.tsx deleted file mode 100644 index 4009beef6b..0000000000 --- a/fission/src/ui/modals/DevtoolZoneRemovalModal.tsx +++ /dev/null @@ -1,110 +0,0 @@ -import { Button, Dialog, DialogActions, DialogContent, DialogTitle, Stack, Typography } from "@mui/material" -import type React from "react" -import { useState } from "react" -import { globalAddToast } from "../components/GlobalUIControls" - -interface DevtoolZoneRemovalModalProps { - isOpen: boolean - onClose: () => void - zoneType: "scoring" | "protected" - zoneName: string - onTemporaryRemoval: () => void - onPermanentRemoval: () => void - actionType?: "removal" | "modification" -} - -const DevtoolZoneRemovalModal: React.FC = ({ - isOpen, - onClose, - zoneType, - zoneName, - onTemporaryRemoval, - onPermanentRemoval, - actionType = "removal", -}) => { - const [isRemoving, setIsRemoving] = useState(false) - - const handleTemporaryRemoval = () => { - onTemporaryRemoval() - onClose() - } - - const handlePermanentRemoval = async () => { - setIsRemoving(true) - try { - await onPermanentRemoval() - const actionText = actionType === "modification" ? "modified" : "removed" - const actionCapitalized = actionType === "modification" ? "Modified" : "Removed" - globalAddToast?.( - "info", - `Zone ${actionCapitalized}`, - `${zoneName} has been permanently ${actionText} in the field file.` - ) - } catch (error) { - const actionText = actionType === "modification" ? "modify" : "remove" - globalAddToast?.( - "error", - `${actionType === "modification" ? "Modification" : "Removal"} Failed`, - `Failed to permanently ${actionText} zone in the field file cache.` - ) - console.error(`Failed to ${actionText} zone in field file:`, error) - } finally { - setIsRemoving(false) - onClose() - } - } - - return ( - - - {actionType === "modification" ? "Modify" : "Remove"} {zoneType === "scoring" ? "Scoring" : "Protected"}{" "} - Zone - - - - - The {zoneType} zone "{zoneName}" was defined in the field file and is cached. - - - Choose how you'd like to{" "} - {actionType === "modification" ? "save your modifications" : "remove it"}: - - - - Temporary {actionType === "modification" ? "modification" : "removal"}:{" "} - {actionType === "modification" - ? "Save changes until next field reload. Original zone will reappear when you refresh or reload the field." - : "Remove zone until next field reload. The zone will reappear when you refresh or reload the field."} - - - Permanent {actionType === "modification" ? "modification" : "removal"}:{" "} - {actionType === "modification" - ? "Save changes to the field file cache. This will persist your modifications on future loads." - : "Remove zone from the field file cache. This will prevent it from reappearing on future loads."} - - - - - - - - - - - ) -} - -export default DevtoolZoneRemovalModal From 99f51e9653ba5d30b012829d0fa6b6f37bc7a894 Mon Sep 17 00:00:00 2001 From: Ryan Zhang Date: Mon, 18 Aug 2025 14:57:09 -0700 Subject: [PATCH 10/16] Update DevtoolZoneUtils for restructure & BaseZonePreferences --- .../systems/preferences/PreferenceTypes.ts | 18 +- fission/src/util/DevtoolZoneUtils.ts | 164 ++++++++---------- 2 files changed, 84 insertions(+), 98 deletions(-) diff --git a/fission/src/systems/preferences/PreferenceTypes.ts b/fission/src/systems/preferences/PreferenceTypes.ts index 72c0b44260..e0fbb7772e 100644 --- a/fission/src/systems/preferences/PreferenceTypes.ts +++ b/fission/src/systems/preferences/PreferenceTypes.ts @@ -147,26 +147,26 @@ export type Alliance = "red" | "blue" export type Station = 1 | 2 | 3 -export type ScoringZonePreferences = { +/** + * Base properties shared by all zone types + */ +export type BaseZonePreferences = { name: string alliance: Alliance parentNode: string | undefined + deltaTransformation: number[] +} + +export type ScoringZonePreferences = BaseZonePreferences & { points: number destroyGamepiece: boolean persistentPoints: boolean - - deltaTransformation: number[] } -export type ProtectedZonePreferences = { - name: string - alliance: Alliance +export type ProtectedZonePreferences = BaseZonePreferences & { penaltyPoints: number - parentNode: string | undefined contactType: ContactType activeDuring: MatchModeType[] - - deltaTransformation: number[] } export type SpawnLocation = Readonly<{ diff --git a/fission/src/util/DevtoolZoneUtils.ts b/fission/src/util/DevtoolZoneUtils.ts index 88ac02ea60..f7811be9eb 100644 --- a/fission/src/util/DevtoolZoneUtils.ts +++ b/fission/src/util/DevtoolZoneUtils.ts @@ -1,16 +1,28 @@ import FieldMiraEditor from "@/mirabuf/FieldMiraEditor" import MirabufCachingService, { MiraType } from "@/mirabuf/MirabufLoader" import PreferencesSystem from "@/systems/preferences/PreferencesSystem" -import type { ProtectedZonePreferences, ScoringZonePreferences } from "@/systems/preferences/PreferenceTypes" +import type { BaseZonePreferences, ProtectedZonePreferences, ScoringZonePreferences } from "@/systems/preferences/PreferenceTypes" import World from "@/systems/World" export type ZoneType = "scoring" | "protected" +/** + * Checks if two zones are equal by comparing their common base properties + */ +function zonesEqual(zone1: BaseZonePreferences, zone2: BaseZonePreferences): boolean { + return ( + zone1.name === zone2.name && + zone1.alliance === zone2.alliance && + zone1.parentNode === zone2.parentNode && + JSON.stringify(zone1.deltaTransformation) === JSON.stringify(zone2.deltaTransformation) + ) +} + /** * Checks if a zone was originally defined in the field file by comparing it with the cached field data. */ export function isZoneFromDevtools( - zone: ScoringZonePreferences | ProtectedZonePreferences, + zone: BaseZonePreferences, zoneType: ZoneType ): boolean { const field = World.sceneRenderer.mirabufSceneObjects.getField() @@ -21,22 +33,14 @@ export function isZoneFromDevtools( const editor = new FieldMiraEditor(parts) - if (zoneType === "scoring") { - const devtoolZones = editor.getUserData("devtool:scoring_zones") as ScoringZonePreferences[] | undefined - if (!devtoolZones) return false - - return devtoolZones.some( - devZone => - devZone.name === zone.name && - devZone.alliance === zone.alliance && - devZone.parentNode === zone.parentNode && - JSON.stringify(devZone.deltaTransformation) === JSON.stringify(zone.deltaTransformation) - ) - } else { - // For protected zones, we'd need to add field file support first - // For now, return false as protected zones don't have field file support yet + if (zoneType === "protected") { return false } + + const devtoolZones = editor.getUserData("devtool:scoring_zones") as ScoringZonePreferences[] | undefined + if (!devtoolZones) return false + + return devtoolZones.some(devZone => zonesEqual(devZone, zone)) } /** @@ -53,47 +57,34 @@ export async function removeZoneFromDevtools( if (!parts) throw new Error("No field parts found") const editor = new FieldMiraEditor(parts) + + if (zoneType === "protected") { + throw new Error("Protected zone field file removal not yet implemented") + } + + const devtoolZones = editor.getUserData("devtool:scoring_zones") as ScoringZonePreferences[] | undefined + if (!devtoolZones) return - if (zoneType === "scoring") { - const devtoolZones = editor.getUserData("devtool:scoring_zones") as ScoringZonePreferences[] | undefined - if (!devtoolZones) return - - // Remove the zone from field file data - const filteredZones = devtoolZones.filter( - devZone => - !( - devZone.name === zone.name && - devZone.alliance === zone.alliance && - devZone.parentNode === zone.parentNode && - JSON.stringify(devZone.deltaTransformation) === JSON.stringify(zone.deltaTransformation) - ) - ) - - // Update the field file data - if (filteredZones.length === 0) { - editor.removeUserData("devtool:scoring_zones") - } else { - editor.setUserData("devtool:scoring_zones", filteredZones) - } + const filteredZones = devtoolZones.filter(devZone => !zonesEqual(devZone, zone)) + if (filteredZones.length === 0) { + editor.removeUserData("devtool:scoring_zones") + } else { + editor.setUserData("devtool:scoring_zones", filteredZones) + } - // Update field preferences to match the filtered field file data - if (field.fieldPreferences) { - field.fieldPreferences.scoringZones = filteredZones - PreferencesSystem.savePreferences?.() - field.updateScoringZones() - } + if (field.fieldPreferences) { + field.fieldPreferences.scoringZones = filteredZones + PreferencesSystem.savePreferences?.() + field.updateScoringZones() + } - // Persist changes to cache - const assembly = field.mirabufInstance.parser.assembly - const cacheId = field.cacheId - if (cacheId) { - const success = await MirabufCachingService.persistDevtoolChanges(cacheId, MiraType.FIELD, assembly) - if (!success) { - throw new Error("Failed to persist changes to cache") - } + const assembly = field.mirabufInstance.parser.assembly + const cacheId = field.cacheId + if (cacheId) { + const success = await MirabufCachingService.persistDevtoolChanges(cacheId, MiraType.FIELD, assembly) + if (!success) { + throw new Error("Failed to persist changes to cache") } - } else { - throw new Error("Protected zone field file removal not yet implemented") } } @@ -112,42 +103,37 @@ export async function modifyZoneInDevtools( if (!parts) throw new Error("No field parts found") const editor = new FieldMiraEditor(parts) - - if (zoneType === "scoring") { - const devtoolZones = editor.getUserData("devtool:scoring_zones") as ScoringZonePreferences[] | undefined - if (!devtoolZones) return - - // Find and replace the zone in field file data - const updatedZones = devtoolZones.map(devZone => { - if ( - devZone.name === originalZone.name && - devZone.alliance === originalZone.alliance && - devZone.parentNode === originalZone.parentNode && - JSON.stringify(devZone.deltaTransformation) === JSON.stringify(originalZone.deltaTransformation) - ) { - return modifiedZone as ScoringZonePreferences - } - return devZone - }) - - editor.setUserData("devtool:scoring_zones", updatedZones) - - if (field.fieldPreferences) { - field.fieldPreferences.scoringZones = updatedZones - PreferencesSystem.savePreferences?.() - field.updateScoringZones() + + if (zoneType === "protected") { + throw new Error("Protected zone field file modification not yet implemented") + } + + const devtoolZones = editor.getUserData("devtool:scoring_zones") as ScoringZonePreferences[] | undefined + if (!devtoolZones) return + + // Find and replace the zone in field file data + const updatedZones = devtoolZones.map(devZone => { + if (zonesEqual(devZone, originalZone)) { + return modifiedZone } + return devZone + }) + + editor.setUserData("devtool:scoring_zones", updatedZones as ScoringZonePreferences[]) - const assembly = field.mirabufInstance.parser.assembly - const cacheId = field.cacheId - if (cacheId) { - const success = await MirabufCachingService.persistDevtoolChanges(cacheId, MiraType.FIELD, assembly) - if (!success) { - throw new Error("Failed to persist changes to cache") - } + if (field.fieldPreferences) { + field.fieldPreferences.scoringZones = updatedZones as ScoringZonePreferences[] + PreferencesSystem.savePreferences?.() + field.updateScoringZones() + } + + const assembly = field.mirabufInstance.parser.assembly + const cacheId = field.cacheId + if (cacheId) { + const success = await MirabufCachingService.persistDevtoolChanges(cacheId, MiraType.FIELD, assembly) + if (!success) { + throw new Error("Failed to persist changes to cache") } - } else { - throw new Error("Protected zone field file modification not yet implemented") } } @@ -163,9 +149,9 @@ export function getDevtoolZones(zoneType: ZoneType): ScoringZonePreferences[] | const editor = new FieldMiraEditor(parts) - if (zoneType === "scoring") { - return editor.getUserData("devtool:scoring_zones") as ScoringZonePreferences[] | undefined - } else { + if (zoneType === "protected") { return undefined } + + return editor.getUserData("devtool:scoring_zones") as ScoringZonePreferences[] | undefined } From 273982ef7d4eecdf035823ab35d2c3ce7d0e3d3f Mon Sep 17 00:00:00 2001 From: Ryan Zhang Date: Mon, 18 Aug 2025 14:57:23 -0700 Subject: [PATCH 11/16] Update interfaces --- .../interfaces/scoring/ManageScoringZonesInterface.tsx | 8 ++++---- .../interfaces/scoring/ScoringZoneConfigInterface.tsx | 9 ++++----- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/fission/src/ui/panels/configuring/assembly-config/interfaces/scoring/ManageScoringZonesInterface.tsx b/fission/src/ui/panels/configuring/assembly-config/interfaces/scoring/ManageScoringZonesInterface.tsx index d06481d925..432779a5c4 100644 --- a/fission/src/ui/panels/configuring/assembly-config/interfaces/scoring/ManageScoringZonesInterface.tsx +++ b/fission/src/ui/panels/configuring/assembly-config/interfaces/scoring/ManageScoringZonesInterface.tsx @@ -9,7 +9,7 @@ import World from "@/systems/World" import Label from "@/ui/components/Label" import ScrollView from "@/ui/components/ScrollView" import { AddButton, DeleteButton, EditButton } from "@/ui/components/StyledComponents" -import DevtoolZoneRemovalModal from "@/ui/modals/DevtoolZoneRemovalModal" +import DevtoolZoneModificationModal from "@/ui/modals/DevtoolZoneModificationModal" import { isZoneFromDevtools, removeZoneFromDevtools } from "@/util/DevtoolZoneUtils" const saveZones = (zones: ScoringZonePreferences[] | undefined, field: MirabufSceneObject | undefined) => { @@ -176,13 +176,13 @@ const ManageZonesInterface: React.FC = ({ selectedField, init selectZone(newZone) })} - ) diff --git a/fission/src/ui/panels/configuring/assembly-config/interfaces/scoring/ScoringZoneConfigInterface.tsx b/fission/src/ui/panels/configuring/assembly-config/interfaces/scoring/ScoringZoneConfigInterface.tsx index 5234b49c30..b73784809f 100644 --- a/fission/src/ui/panels/configuring/assembly-config/interfaces/scoring/ScoringZoneConfigInterface.tsx +++ b/fission/src/ui/panels/configuring/assembly-config/interfaces/scoring/ScoringZoneConfigInterface.tsx @@ -16,7 +16,7 @@ import SelectButton from "@/ui/components/SelectButton" import TransformGizmoControl from "@/ui/components/TransformGizmoControl" import type { Panel } from "@/ui/helpers/UIProviderHelpers" import { CloseType, useUIContext } from "@/ui/helpers/UIProviderHelpers" -import DevtoolZoneRemovalModal from "@/ui/modals/DevtoolZoneRemovalModal" +import DevtoolZoneModificationModal from "@/ui/modals/DevtoolZoneModificationModal" import { isZoneFromDevtools, modifyZoneInDevtools } from "@/util/DevtoolZoneUtils" import { convertArrayToThreeMatrix4, @@ -380,14 +380,13 @@ const ZoneConfigInterface: React.FC = ({ selectedField, selecte {gizmoComponent} - {/** Custom Save/Cancel buttons that replace the panel's default buttons */} From 9527d0a60ce3f168a824aa3599d0a6040f8a9310 Mon Sep 17 00:00:00 2001 From: Ryan Zhang Date: Mon, 18 Aug 2025 15:07:54 -0700 Subject: [PATCH 12/16] chore: formatting --- .../modals/DevtoolZoneModificationModal.tsx | 16 ++++++-------- fission/src/util/DevtoolZoneUtils.ts | 21 ++++++++++--------- 2 files changed, 17 insertions(+), 20 deletions(-) diff --git a/fission/src/ui/modals/DevtoolZoneModificationModal.tsx b/fission/src/ui/modals/DevtoolZoneModificationModal.tsx index 76b3110193..a8d96debe0 100644 --- a/fission/src/ui/modals/DevtoolZoneModificationModal.tsx +++ b/fission/src/ui/modals/DevtoolZoneModificationModal.tsx @@ -31,11 +31,7 @@ const DevtoolZoneModificationModal: React.FC setIsModifying(true) try { await onPermanentModification() - globalAddToast?.( - "info", - "Zone Modified", - `${zoneName} has been permanently modified in the field file.` - ) + globalAddToast?.("info", "Zone Modified", `${zoneName} has been permanently modified in the field file.`) } catch (error) { globalAddToast?.( "error", @@ -51,9 +47,7 @@ const DevtoolZoneModificationModal: React.FC return ( - - Modify {zoneType === "scoring" ? "Scoring" : "Protected"} Zone - + Modify {zoneType === "scoring" ? "Scoring" : "Protected"} Zone @@ -64,10 +58,12 @@ const DevtoolZoneModificationModal: React.FC - Temporary modification: Save changes until next field reload. Original zone will reappear when you refresh the page. + Temporary modification: Save changes until next field reload. Original zone + will reappear when you refresh the page. - Permanent modification: Save changes to the local asset file. This will persist your modifications until you remove it from the cache. + Permanent modification: Save changes to the local asset file. This will + persist your modifications until you remove it from the cache. diff --git a/fission/src/util/DevtoolZoneUtils.ts b/fission/src/util/DevtoolZoneUtils.ts index f7811be9eb..19e82f1989 100644 --- a/fission/src/util/DevtoolZoneUtils.ts +++ b/fission/src/util/DevtoolZoneUtils.ts @@ -1,7 +1,11 @@ import FieldMiraEditor from "@/mirabuf/FieldMiraEditor" import MirabufCachingService, { MiraType } from "@/mirabuf/MirabufLoader" import PreferencesSystem from "@/systems/preferences/PreferencesSystem" -import type { BaseZonePreferences, ProtectedZonePreferences, ScoringZonePreferences } from "@/systems/preferences/PreferenceTypes" +import type { + BaseZonePreferences, + ProtectedZonePreferences, + ScoringZonePreferences, +} from "@/systems/preferences/PreferenceTypes" import World from "@/systems/World" export type ZoneType = "scoring" | "protected" @@ -21,10 +25,7 @@ function zonesEqual(zone1: BaseZonePreferences, zone2: BaseZonePreferences): boo /** * Checks if a zone was originally defined in the field file by comparing it with the cached field data. */ -export function isZoneFromDevtools( - zone: BaseZonePreferences, - zoneType: ZoneType -): boolean { +export function isZoneFromDevtools(zone: BaseZonePreferences, zoneType: ZoneType): boolean { const field = World.sceneRenderer.mirabufSceneObjects.getField() if (!field) return false @@ -36,7 +37,7 @@ export function isZoneFromDevtools( if (zoneType === "protected") { return false } - + const devtoolZones = editor.getUserData("devtool:scoring_zones") as ScoringZonePreferences[] | undefined if (!devtoolZones) return false @@ -57,11 +58,11 @@ export async function removeZoneFromDevtools( if (!parts) throw new Error("No field parts found") const editor = new FieldMiraEditor(parts) - + if (zoneType === "protected") { throw new Error("Protected zone field file removal not yet implemented") } - + const devtoolZones = editor.getUserData("devtool:scoring_zones") as ScoringZonePreferences[] | undefined if (!devtoolZones) return @@ -103,11 +104,11 @@ export async function modifyZoneInDevtools( if (!parts) throw new Error("No field parts found") const editor = new FieldMiraEditor(parts) - + if (zoneType === "protected") { throw new Error("Protected zone field file modification not yet implemented") } - + const devtoolZones = editor.getUserData("devtool:scoring_zones") as ScoringZonePreferences[] | undefined if (!devtoolZones) return From 72888a12c1762befd2f310b02c7e36d13205863b Mon Sep 17 00:00:00 2001 From: Ryan Zhang Date: Tue, 19 Aug 2025 09:22:44 -0700 Subject: [PATCH 13/16] chore: formatting --- fission/src/mirabuf/FieldMiraEditor.ts | 2 ++ fission/src/util/DevtoolZoneUtils.ts | 29 +++++++++++++++++++------- 2 files changed, 24 insertions(+), 7 deletions(-) diff --git a/fission/src/mirabuf/FieldMiraEditor.ts b/fission/src/mirabuf/FieldMiraEditor.ts index 4955fc42a2..ec659c27b9 100644 --- a/fission/src/mirabuf/FieldMiraEditor.ts +++ b/fission/src/mirabuf/FieldMiraEditor.ts @@ -3,11 +3,13 @@ import { mirabuf } from "@/proto/mirabuf" import { defaultFieldPreferences, type FieldPreferences, + type ProtectedZonePreferences, type ScoringZonePreferences, } from "@/systems/preferences/PreferenceTypes" export interface DevtoolMiraData { "devtool:scoring_zones": ScoringZonePreferences[] + "devtool:protected_zones": ProtectedZonePreferences[] "devtool:camera_locations": unknown "devtool:spawn_locations": FieldPreferences["spawnLocations"] "devtool:a": unknown diff --git a/fission/src/util/DevtoolZoneUtils.ts b/fission/src/util/DevtoolZoneUtils.ts index 19e82f1989..a72a8ab4e3 100644 --- a/fission/src/util/DevtoolZoneUtils.ts +++ b/fission/src/util/DevtoolZoneUtils.ts @@ -38,7 +38,7 @@ export function isZoneFromDevtools(zone: BaseZonePreferences, zoneType: ZoneType return false } - const devtoolZones = editor.getUserData("devtool:scoring_zones") as ScoringZonePreferences[] | undefined + const devtoolZones = editor.getUserData("devtool:scoring_zones") if (!devtoolZones) return false return devtoolZones.some(devZone => zonesEqual(devZone, zone)) @@ -47,6 +47,8 @@ export function isZoneFromDevtools(zone: BaseZonePreferences, zoneType: ZoneType /** * Removes a zone from the field file cache permanently. */ +export async function removeZoneFromDevtools(zone: ScoringZonePreferences, zoneType: "scoring"): Promise +export async function removeZoneFromDevtools(zone: ProtectedZonePreferences, zoneType: "protected"): Promise export async function removeZoneFromDevtools( zone: ScoringZonePreferences | ProtectedZonePreferences, zoneType: ZoneType @@ -63,7 +65,7 @@ export async function removeZoneFromDevtools( throw new Error("Protected zone field file removal not yet implemented") } - const devtoolZones = editor.getUserData("devtool:scoring_zones") as ScoringZonePreferences[] | undefined + const devtoolZones = editor.getUserData("devtool:scoring_zones") if (!devtoolZones) return const filteredZones = devtoolZones.filter(devZone => !zonesEqual(devZone, zone)) @@ -92,6 +94,16 @@ export async function removeZoneFromDevtools( /** * Modifies a zone in the field file cache permanently by replacing it with updated data. */ +export async function modifyZoneInDevtools( + originalZone: ScoringZonePreferences, + modifiedZone: ScoringZonePreferences, + zoneType: "scoring" +): Promise +export async function modifyZoneInDevtools( + originalZone: ProtectedZonePreferences, + modifiedZone: ProtectedZonePreferences, + zoneType: "protected" +): Promise export async function modifyZoneInDevtools( originalZone: ScoringZonePreferences | ProtectedZonePreferences, modifiedZone: ScoringZonePreferences | ProtectedZonePreferences, @@ -109,21 +121,22 @@ export async function modifyZoneInDevtools( throw new Error("Protected zone field file modification not yet implemented") } - const devtoolZones = editor.getUserData("devtool:scoring_zones") as ScoringZonePreferences[] | undefined + const devtoolZones = editor.getUserData("devtool:scoring_zones") if (!devtoolZones) return // Find and replace the zone in field file data + // Since we're in the scoring branch, we know modifiedZone is ScoringZonePreferences const updatedZones = devtoolZones.map(devZone => { if (zonesEqual(devZone, originalZone)) { - return modifiedZone + return modifiedZone as ScoringZonePreferences } return devZone }) - editor.setUserData("devtool:scoring_zones", updatedZones as ScoringZonePreferences[]) + editor.setUserData("devtool:scoring_zones", updatedZones) if (field.fieldPreferences) { - field.fieldPreferences.scoringZones = updatedZones as ScoringZonePreferences[] + field.fieldPreferences.scoringZones = updatedZones PreferencesSystem.savePreferences?.() field.updateScoringZones() } @@ -141,6 +154,8 @@ export async function modifyZoneInDevtools( /** * Gets all zones that exist in the field file for a given type. */ +export function getDevtoolZones(zoneType: "scoring"): ScoringZonePreferences[] | undefined +export function getDevtoolZones(zoneType: "protected"): ProtectedZonePreferences[] | undefined export function getDevtoolZones(zoneType: ZoneType): ScoringZonePreferences[] | ProtectedZonePreferences[] | undefined { const field = World.sceneRenderer.mirabufSceneObjects.getField() if (!field) return undefined @@ -154,5 +169,5 @@ export function getDevtoolZones(zoneType: ZoneType): ScoringZonePreferences[] | return undefined } - return editor.getUserData("devtool:scoring_zones") as ScoringZonePreferences[] | undefined + return editor.getUserData("devtool:scoring_zones") } From 232faca04f8a16910e9b1633ce6acd60ff829931 Mon Sep 17 00:00:00 2001 From: Ryan Zhang Date: Tue, 19 Aug 2025 11:24:17 -0700 Subject: [PATCH 14/16] fix: save/cancel button dissappearing --- .../scoring/ConfigureProtectedZonesInterface.tsx | 5 ++++- .../scoring/ConfigureScoringZonesInterface.tsx | 1 + .../scoring/ManageProtectedZonesInterface.tsx | 14 +++++++++++++- .../scoring/ManageScoringZonesInterface.tsx | 14 +++++++++++++- 4 files changed, 31 insertions(+), 3 deletions(-) diff --git a/fission/src/ui/panels/configuring/assembly-config/interfaces/scoring/ConfigureProtectedZonesInterface.tsx b/fission/src/ui/panels/configuring/assembly-config/interfaces/scoring/ConfigureProtectedZonesInterface.tsx index 550cbb4c7b..6ff216e348 100644 --- a/fission/src/ui/panels/configuring/assembly-config/interfaces/scoring/ConfigureProtectedZonesInterface.tsx +++ b/fission/src/ui/panels/configuring/assembly-config/interfaces/scoring/ConfigureProtectedZonesInterface.tsx @@ -5,6 +5,7 @@ import type MirabufSceneObject from "@/mirabuf/MirabufSceneObject" import PreferencesSystem from "@/systems/preferences/PreferencesSystem" import type { ProtectedZonePreferences } from "@/systems/preferences/PreferenceTypes" import Label from "@/ui/components/Label" +import type { Panel } from "@/ui/helpers/UIProviderHelpers" import ManageProtectedZonesInterface from "./ManageProtectedZonesInterface" import ZoneConfigInterface from "./ProtectedZoneConfigInterface" @@ -21,9 +22,10 @@ const protectedZones = (zones: ProtectedZonePreferences[] | undefined, field: Mi interface ConfigureZonesProps { selectedField: MirabufSceneObject initialZones: ProtectedZonePreferences[] + panel?: Panel } -const ConfigureProtectedZonesInterface: React.FC = ({ selectedField, initialZones }) => { +const ConfigureProtectedZonesInterface: React.FC = ({ selectedField, initialZones, panel }) => { const [selectedZone, setSelectedZone] = useState(undefined) return ( @@ -33,6 +35,7 @@ const ConfigureProtectedZonesInterface: React.FC = ({ selec selectedField={selectedField} initialZones={initialZones} selectZone={setSelectedZone} + panel={panel} /> ) : ( <> diff --git a/fission/src/ui/panels/configuring/assembly-config/interfaces/scoring/ConfigureScoringZonesInterface.tsx b/fission/src/ui/panels/configuring/assembly-config/interfaces/scoring/ConfigureScoringZonesInterface.tsx index 90369255cd..b9b508748c 100644 --- a/fission/src/ui/panels/configuring/assembly-config/interfaces/scoring/ConfigureScoringZonesInterface.tsx +++ b/fission/src/ui/panels/configuring/assembly-config/interfaces/scoring/ConfigureScoringZonesInterface.tsx @@ -39,6 +39,7 @@ const ConfigureScoringZonesInterface: React.FC = ({ selecte selectedField={selectedField} initialZones={initialZones} selectZone={setSelectedZone} + panel={panel} /> ) : ( <> diff --git a/fission/src/ui/panels/configuring/assembly-config/interfaces/scoring/ManageProtectedZonesInterface.tsx b/fission/src/ui/panels/configuring/assembly-config/interfaces/scoring/ManageProtectedZonesInterface.tsx index 47151ecaf8..78d9fc2c40 100644 --- a/fission/src/ui/panels/configuring/assembly-config/interfaces/scoring/ManageProtectedZonesInterface.tsx +++ b/fission/src/ui/panels/configuring/assembly-config/interfaces/scoring/ManageProtectedZonesInterface.tsx @@ -11,6 +11,8 @@ import World from "@/systems/World" import Label from "@/ui/components/Label" import ScrollView from "@/ui/components/ScrollView" import { AddButton, DeleteButton, EditButton } from "@/ui/components/StyledComponents" +import type { Panel } from "@/ui/helpers/UIProviderHelpers" +import { useUIContext } from "@/ui/helpers/UIProviderHelpers" const saveZones = (zones: ProtectedZonePreferences[] | undefined, field: MirabufSceneObject | undefined) => { if (!zones || !field) return @@ -59,11 +61,21 @@ interface ProtectedZonesProps { selectedField: MirabufSceneObject initialZones: ProtectedZonePreferences[] selectZone: (zone: ProtectedZonePreferences) => void + panel?: Panel } -const ManageZonesInterface: React.FC = ({ selectedField, initialZones, selectZone }) => { +const ManageZonesInterface: React.FC = ({ selectedField, initialZones, selectZone, panel }) => { const [zones, setZones] = useState(initialZones) + const { configureScreen } = useUIContext() + + // Show the panel's default footer buttons when this interface is active + useEffect(() => { + if (panel) { + configureScreen(panel, { hideAccept: false, hideCancel: false }, {}) + } + }, [panel, configureScreen]) + const saveEvent = useCallback(() => { saveZones(zones, selectedField) }, [zones, selectedField]) diff --git a/fission/src/ui/panels/configuring/assembly-config/interfaces/scoring/ManageScoringZonesInterface.tsx b/fission/src/ui/panels/configuring/assembly-config/interfaces/scoring/ManageScoringZonesInterface.tsx index 432779a5c4..6d9085b81e 100644 --- a/fission/src/ui/panels/configuring/assembly-config/interfaces/scoring/ManageScoringZonesInterface.tsx +++ b/fission/src/ui/panels/configuring/assembly-config/interfaces/scoring/ManageScoringZonesInterface.tsx @@ -11,6 +11,8 @@ import ScrollView from "@/ui/components/ScrollView" import { AddButton, DeleteButton, EditButton } from "@/ui/components/StyledComponents" import DevtoolZoneModificationModal from "@/ui/modals/DevtoolZoneModificationModal" import { isZoneFromDevtools, removeZoneFromDevtools } from "@/util/DevtoolZoneUtils" +import type { Panel } from "@/ui/helpers/UIProviderHelpers" +import { useUIContext } from "@/ui/helpers/UIProviderHelpers" const saveZones = (zones: ScoringZonePreferences[] | undefined, field: MirabufSceneObject | undefined) => { if (!zones || !field) return @@ -73,9 +75,10 @@ interface ScoringZonesProps { selectedField: MirabufSceneObject initialZones: ScoringZonePreferences[] selectZone: (zone: ScoringZonePreferences) => void + panel?: Panel } -const ManageZonesInterface: React.FC = ({ selectedField, initialZones, selectZone }) => { +const ManageZonesInterface: React.FC = ({ selectedField, initialZones, selectZone, panel }) => { const [zones, setZones] = useState(initialZones) const [confirmationModal, setConfirmationModal] = useState<{ isOpen: boolean @@ -83,6 +86,15 @@ const ManageZonesInterface: React.FC = ({ selectedField, init zoneIndex: number }>({ isOpen: false, zone: null, zoneIndex: -1 }) + const { configureScreen } = useUIContext() + + // Show the panel's default footer buttons when this interface is active + useEffect(() => { + if (panel) { + configureScreen(panel, { hideAccept: false, hideCancel: false }, {}) + } + }, [panel, configureScreen]) + const saveEvent = useCallback(() => { saveZones(zones, selectedField) }, [zones, selectedField]) From 0aba2ae94040d52bcef0398faaa69c19330a63c4 Mon Sep 17 00:00:00 2001 From: Ryan Zhang Date: Wed, 20 Aug 2025 10:34:25 -0700 Subject: [PATCH 15/16] Add caching for manual scoring zones --- .../scoring/ScoringZoneConfigInterface.tsx | 26 +++++-- fission/src/util/DevtoolZoneUtils.ts | 69 ++++++++++++++++++- 2 files changed, 90 insertions(+), 5 deletions(-) diff --git a/fission/src/ui/panels/configuring/assembly-config/interfaces/scoring/ScoringZoneConfigInterface.tsx b/fission/src/ui/panels/configuring/assembly-config/interfaces/scoring/ScoringZoneConfigInterface.tsx index b73784809f..eb6b21d3b8 100644 --- a/fission/src/ui/panels/configuring/assembly-config/interfaces/scoring/ScoringZoneConfigInterface.tsx +++ b/fission/src/ui/panels/configuring/assembly-config/interfaces/scoring/ScoringZoneConfigInterface.tsx @@ -17,7 +17,7 @@ import TransformGizmoControl from "@/ui/components/TransformGizmoControl" import type { Panel } from "@/ui/helpers/UIProviderHelpers" import { CloseType, useUIContext } from "@/ui/helpers/UIProviderHelpers" import DevtoolZoneModificationModal from "@/ui/modals/DevtoolZoneModificationModal" -import { isZoneFromDevtools, modifyZoneInDevtools } from "@/util/DevtoolZoneUtils" +import { addUserZoneToDevtools, isZoneFromDevtools, modifyZoneInDevtools, zonesEqual } from "@/util/DevtoolZoneUtils" import { convertArrayToThreeMatrix4, convertJoltMat44ToThreeMatrix4, @@ -151,7 +151,7 @@ const ZoneConfigInterface: React.FC = ({ selectedField, selecte } }, [panel, configureScreen]) - const handleSave = useCallback(() => { + const handleSave = useCallback(async () => { if (gizmoRef.current && selectedField) { save( selectedField, @@ -164,6 +164,24 @@ const ZoneConfigInterface: React.FC = ({ selectedField, selecte gizmoRef.current, selectedNode ) + + // Auto-cache user-created zones for persistence + const isExistingZone = selectedField.fieldPreferences?.scoringZones.some( + z => z === originalZoneRef.current || zonesEqual(z, originalZoneRef.current) + ) + + if (!isZoneFromDevtools(originalZoneRef.current, "scoring")) { + try { + await addUserZoneToDevtools( + selectedZone, + isExistingZone ? originalZoneRef.current : undefined, + "scoring" + ) + } catch (error) { + console.warn("Failed to auto-cache user zone:", error) + } + } + saveAllZones() } }, [selectedField, selectedZone, name, alliance, points, destroy, persistent, selectedNode, saveAllZones]) @@ -403,11 +421,11 @@ const ZoneConfigInterface: React.FC = ({ selectedField, selecte