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 };