Skip to content
Open
Show file tree
Hide file tree
Changes from 10 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
110 changes: 110 additions & 0 deletions fission/src/ui/modals/DevtoolZoneRemovalModal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
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<DevtoolZoneRemovalModalProps> = ({
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should probably be renamed if this is being generalized

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 (
<Dialog open={isOpen} onClose={onClose} maxWidth="sm" fullWidth>
<DialogTitle>
{actionType === "modification" ? "Modify" : "Remove"} {zoneType === "scoring" ? "Scoring" : "Protected"}{" "}
Zone
</DialogTitle>
<DialogContent>
<Stack spacing={2}>
<Typography variant="body1">
The {zoneType} zone "{zoneName}" was defined in the field file and is cached.
</Typography>
<Typography variant="body2" color="text.secondary">
Choose how you'd like to{" "}
{actionType === "modification" ? "save your modifications" : "remove it"}:
</Typography>
<Stack spacing={1}>
<Typography variant="body2">
<strong>Temporary {actionType === "modification" ? "modification" : "removal"}:</strong>{" "}
{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."}
</Typography>
<Typography variant="body2">
<strong>Permanent {actionType === "modification" ? "modification" : "removal"}:</strong>{" "}
{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."}
</Typography>
</Stack>
</Stack>
</DialogContent>
<DialogActions>
<Button onClick={onClose} disabled={isRemoving}>
Cancel
</Button>
<Button onClick={handleTemporaryRemoval} disabled={isRemoving} variant="outlined" color="warning">
Temporary {actionType === "modification" ? "Modification" : "Removal"}
</Button>
<Button
onClick={handlePermanentRemoval}
disabled={isRemoving}
variant="contained"
color={actionType === "modification" ? "primary" : "error"}
>
{isRemoving
? `${actionType === "modification" ? "Modifying" : "Removing"}...`
: `Permanent ${actionType === "modification" ? "Modification" : "Removal"}`}
</Button>
</DialogActions>
</Dialog>
)
}

export default DevtoolZoneRemovalModal
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -79,7 +79,13 @@ const ConfigInterface: React.FC<ConfigInterfaceProps<void, ConfigurePanelCustomP
console.error("Field does not contain scoring zone preferences!")
return <Label size="md">ERROR: Field does not contain scoring zone configuration!</Label>
}
return <ConfigureScoringZonesInterface selectedField={assembly} initialZones={zones} />
return (
<ConfigureScoringZonesInterface
panel={panel as Panel<any, any>}
selectedField={assembly}
initialZones={zones}
/>
)
}
case ConfigMode.PROTECTED_ZONES: {
const zones = assembly.fieldPreferences?.protectedZones ?? []
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,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<any, any>
}

const ConfigureScoringZonesInterface: React.FC<ConfigureZonesProps> = ({ selectedField, initialZones }) => {
const ConfigureScoringZonesInterface: React.FC<ConfigureZonesProps> = ({ selectedField, initialZones, panel }) => {
const [selectedZone, setSelectedZone] = useState<ScoringZonePreferences | undefined>(undefined)

return (
Expand Down Expand Up @@ -66,6 +69,7 @@ const ConfigureScoringZonesInterface: React.FC<ConfigureZonesProps> = ({ selecte
saveAllZones={() => {
saveZones(selectedField.fieldPreferences?.scoringZones, selectedField)
}}
panel={panel}
/>
</>
)}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -25,9 +27,18 @@ type ScoringZoneRowProps = {
save: () => void
deleteZone: () => void
selectZone: (zone: ScoringZonePreferences) => void
onShowConfirmation: (zone: ScoringZonePreferences) => void
}

const ScoringZoneRow: React.FC<ScoringZoneRowProps> = ({ zone, save, deleteZone, selectZone }) => {
const ScoringZoneRow: React.FC<ScoringZoneRowProps> = ({ zone, save, deleteZone, selectZone, onShowConfirmation }) => {
const handleDeleteClick = () => {
if (isZoneFromDevtools(zone, "scoring")) {
onShowConfirmation(zone)
} else {
deleteZone()
}
}

return (
<Stack justifyContent={"space-between"} alignItems={"center"} gap={"1rem"}>
<Stack direction="row" gap={8}>
Expand All @@ -51,7 +62,7 @@ const ScoringZoneRow: React.FC<ScoringZoneRowProps> = ({ zone, save, deleteZone,
})}

{DeleteButton(() => {
deleteZone()
handleDeleteClick()
})}
</Stack>
</Stack>
Expand All @@ -66,6 +77,11 @@ interface ScoringZonesProps {

const ManageZonesInterface: React.FC<ScoringZonesProps> = ({ selectedField, initialZones, selectZone }) => {
const [zones, setZones] = useState<ScoringZonePreferences[]>(initialZones)
const [confirmationModal, setConfirmationModal] = useState<{
isOpen: boolean
zone: ScoringZonePreferences | null
zoneIndex: number
}>({ isOpen: false, zone: null, zoneIndex: -1 })

const saveEvent = useCallback(() => {
saveZones(zones, selectedField)
Expand All @@ -89,6 +105,31 @@ const ManageZonesInterface: React.FC<ScoringZonesProps> = ({ 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 ? (
Expand All @@ -109,6 +150,7 @@ const ManageZonesInterface: React.FC<ScoringZonesProps> = ({ selectedField, init
)
}}
selectZone={selectZone}
onShowConfirmation={handleShowConfirmation}
/>
))}
</Stack>
Expand All @@ -133,6 +175,15 @@ const ManageZonesInterface: React.FC<ScoringZonesProps> = ({ selectedField, init

selectZone(newZone)
})}

<DevtoolZoneRemovalModal
isOpen={confirmationModal.isOpen}
onClose={handleCloseConfirmation}
zoneType="scoring"
zoneName={confirmationModal.zone?.name ?? ""}
onTemporaryRemoval={handleTemporaryRemoval}
onPermanentRemoval={handlePermanentRemoval}
/>
</>
)
}
Expand Down
Loading
Loading