diff --git a/src/ScriptEditor/ui/ScriptEditorRoot.tsx b/src/ScriptEditor/ui/ScriptEditorRoot.tsx index 238b878e6..74c8f633c 100644 --- a/src/ScriptEditor/ui/ScriptEditorRoot.tsx +++ b/src/ScriptEditor/ui/ScriptEditorRoot.tsx @@ -25,7 +25,7 @@ import { PromptEvent } from "../../ui/React/PromptManager"; import { useRerender } from "../../ui/React/hooks"; -import { dirty, getServerCode, makeModel } from "./utils"; +import { isUnsavedFile, getServerCode, makeModel } from "./utils"; import { OpenScript } from "./OpenScript"; import { Tabs } from "./Tabs"; import { Toolbar } from "./Toolbar"; @@ -38,6 +38,9 @@ import { RamCalculationErrorCode } from "../../Script/RamCalculationErrorCodes"; import { hasScriptExtension, isLegacyScript, type ScriptFilePath } from "../../Paths/ScriptFilePath"; import { exceptionAlert } from "../../utils/helpers/exceptionAlert"; import type { BaseServer } from "../../Server/BaseServer"; +import { SpecialServers } from "../../Server/data/SpecialServers"; +import { SnackbarEvents } from "../../ui/React/Snackbar"; +import { ToastVariant } from "@enums"; // Extend acorn-walk to support TypeScript nodes. extendAcornWalkForTypeScriptNodes(walk.base); @@ -206,13 +209,7 @@ function Root(props: IProps): React.ReactElement { return; } - - const server = GetServer(currentScript.hostname); - if (server === null) throw new Error("Server should not be null but it is."); - server.writeToContentFile(currentScript.path, currentScript.code); - if (Settings.SaveGameOnFileSave) { - saveObject.saveGame().catch((error) => exceptionAlert(error)); - } + saveScript(currentScript); rerender(); }, [rerender]); @@ -363,7 +360,13 @@ function Root(props: IProps): React.ReactElement { function saveScript(scriptToSave: OpenScript): void { const server = GetServer(scriptToSave.hostname); - if (!server) throw new Error("Server should not be null but it is."); + if (!server) { + throw new Error("Server should not be null but it is."); + } + // Show a warning message if the file is on a non-home server. + if (scriptToSave.hostname !== SpecialServers.Home) { + SnackbarEvents.emit("You saved a file on a non-home server!", ToastVariant.WARNING, 3000); + } // This server helper already handles overwriting, etc. server.writeToContentFile(scriptToSave.path, scriptToSave.code); if (Settings.SaveGameOnFileSave) { @@ -411,7 +414,7 @@ function Root(props: IProps): React.ReactElement { const savedScriptCode = closingScript.code; const wasCurrentScript = openScripts[index] === currentScript; - if (dirty(openScripts, index)) { + if (isUnsavedFile(openScripts, index)) { PromptEvent.emit({ txt: `Do you want to save changes to ${closingScript.path} on ${closingScript.hostname}?`, resolve: (result: boolean | string) => { diff --git a/src/ScriptEditor/ui/Tab.tsx b/src/ScriptEditor/ui/Tab.tsx index 687b65964..79560c821 100644 --- a/src/ScriptEditor/ui/Tab.tsx +++ b/src/ScriptEditor/ui/Tab.tsx @@ -3,6 +3,7 @@ import { DraggableProvided } from "react-beautiful-dnd"; import Button from "@mui/material/Button"; import Tooltip from "@mui/material/Tooltip"; +import Typography from "@mui/material/Typography"; import SyncIcon from "@mui/icons-material/Sync"; import CloseIcon from "@mui/icons-material/Close"; @@ -11,9 +12,10 @@ import { Settings } from "../../Settings/Settings"; interface IProps { provided: DraggableProvided; - title: string; + fullPath: string; isActive: boolean; isExternal: boolean; + isUnsaved: boolean; onClick: () => void; onClose: () => void; @@ -24,7 +26,7 @@ const tabMargin = 5; const tabIconWidth = 25; const tabIconHeight = 38.5; -export function Tab({ provided, title, isActive, isExternal, onClick, onClose, onUpdate }: IProps) { +export function Tab({ provided, fullPath, isActive, isExternal, isUnsaved, onClick, onClose, onUpdate }: IProps) { const colorProps = isActive ? { background: Settings.theme.button, @@ -37,8 +39,35 @@ export function Tab({ provided, title, isActive, isExternal, onClick, onClose, o color: Settings.theme.secondary, }; + let tabTitle; + let tooltipTitle; + if (isUnsaved) { + // Show a blinking "*" character to notify the player that this file is dirtied. + tabTitle = ( + <> + + *{" "} + + {fullPath} + + ); + } else { + tabTitle = fullPath; + } + if (isExternal) { - colorProps.color = Settings.theme.info; + colorProps.color = Settings.theme.warning; + // Show a warning message if this file is on a non-home server. + tooltipTitle = ( + + {tabTitle} +
+ This file is on a non-home server. You will lose all files on non-home servers when they are deleted or + recreated (install augmentations, soft reset, deleted by NS APIs, etc.). +
+ ); + } else { + tooltipTitle = tabTitle; } const iconButtonStyle = { maxWidth: tabIconWidth, @@ -71,12 +100,14 @@ export function Tab({ provided, title, isActive, isExternal, onClick, onClose, o border: "1px solid " + Settings.theme.well, }} > - + diff --git a/src/ScriptEditor/ui/Tabs.tsx b/src/ScriptEditor/ui/Tabs.tsx index 3454d9872..e4b9f1ef5 100644 --- a/src/ScriptEditor/ui/Tabs.tsx +++ b/src/ScriptEditor/ui/Tabs.tsx @@ -13,9 +13,10 @@ import SearchIcon from "@mui/icons-material/Search"; import { useBoolean, useRerender } from "../../ui/React/hooks"; import { Settings } from "../../Settings/Settings"; -import { dirty, reorder } from "./utils"; +import { isUnsavedFile, reorder } from "./utils"; import { OpenScript } from "./OpenScript"; import { Tab } from "./Tab"; +import { SpecialServers } from "../../Server/data/SpecialServers"; const tabsMaxWidth = 1640; const searchWidth = 180; @@ -119,22 +120,16 @@ export function Tabs({ scripts, currentScript, onTabClick, onTabClose, onTabUpda {filteredScripts.map(({ script, originalIndex }, index) => { const { path: fileName, hostname } = script; const isActive = currentScript?.path === script.path && currentScript.hostname === script.hostname; - - const title = `${hostname}:~${fileName.startsWith("/") ? "" : "/"}${fileName} ${dirty(scripts, index)}`; - + const fullPath = `${hostname}:/${fileName}`; return ( - + {(provided) => ( onTabClick(originalIndex)} onClose={() => onTabClose(originalIndex)} onUpdate={() => onTabUpdate(originalIndex)} diff --git a/src/ScriptEditor/ui/utils.ts b/src/ScriptEditor/ui/utils.ts index c1e4f04a1..30b879b53 100644 --- a/src/ScriptEditor/ui/utils.ts +++ b/src/ScriptEditor/ui/utils.ts @@ -12,11 +12,13 @@ function getServerCode(scripts: OpenScript[], index: number): string | null { return data; } -function dirty(scripts: OpenScript[], index: number): string { +function isUnsavedFile(scripts: OpenScript[], index: number): boolean { const openScript = scripts[index]; const serverData = getServerCode(scripts, index); - if (serverData === null) return " *"; - return serverData !== openScript.code ? " *" : ""; + if (serverData === null) { + return true; + } + return serverData !== openScript.code; } function reorder(list: unknown[], startIndex: number, endIndex: number): void { @@ -60,4 +62,4 @@ function makeModel(hostname: string, filename: string, code: string): editor.ITe return editor.createModel(code, language, uri); } -export { getServerCode, dirty, reorder, makeModel }; +export { getServerCode, isUnsavedFile, reorder, makeModel };