Skip to content

Commit 5f85cc4

Browse files
committed
Unify close behavior and fix untitled unsaved save flow
1 parent 725dc51 commit 5f85cc4

File tree

2 files changed

+53
-57
lines changed

2 files changed

+53
-57
lines changed

src/renderer/renderer.js

Lines changed: 41 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import {
1818
CompletionContext,
1919
hoverTooltip,
2020
acceptCompletion,
21+
rectangularSelection,
2122
} from "../../third_party/codemirror/cm.js";
2223
import { ABC2SVG_DECORATIONS } from "./abc_decorations_abc2svg.js";
2324
import { initSettings } from "./settings.js";
@@ -235,6 +236,7 @@ const abcCompletionCompartment = new Compartment();
235236
const abcHoverCompartment = new Compartment();
236237
const abcTuningModeCompartment = new Compartment();
237238
const abcPayloadReadOnlyCompartment = new Compartment();
239+
const UNTITLED_UNSAVED_LABEL = "Untitled (unsaved)";
238240

239241
function buildAbcCompletionSource() {
240242
const keyOptions = [
@@ -4559,16 +4561,16 @@ function setFileNameMeta(name) {
45594561
updateWindowTitle();
45604562
}
45614563

4562-
function updateWindowTitle() {
4563-
const tuneDirty = Boolean(currentDoc && currentDoc.dirty);
4564-
const dirtyTag = (tuneDirty || headerDirty) ? "*" : "";
4565-
const filePath = (currentDoc && currentDoc.path) ? String(currentDoc.path) : "";
4566-
const fileNameWithExt = filePath ? safeBasename(filePath) : "Untitled.abc";
4567-
const dirPath = filePath ? safeDirname(filePath) : (libraryIndex && libraryIndex.root ? String(libraryIndex.root) : "");
4568-
const dirShort = formatPathTail(dirPath, 3);
4569-
const display = dirShort ? `${dirShort}/${fileNameWithExt}` : fileNameWithExt;
4570-
document.title = `ABCarus — ${display}${dirtyTag}`;
4571-
}
4564+
function updateWindowTitle() {
4565+
const tuneDirty = Boolean(currentDoc && currentDoc.dirty) || Boolean(isNewTuneDraft);
4566+
const dirtyTag = (tuneDirty || headerDirty) ? "*" : "";
4567+
const filePath = (currentDoc && currentDoc.path) ? String(currentDoc.path) : "";
4568+
const fileNameWithExt = filePath ? safeBasename(filePath) : UNTITLED_UNSAVED_LABEL;
4569+
const dirPath = filePath ? safeDirname(filePath) : (libraryIndex && libraryIndex.root ? String(libraryIndex.root) : "");
4570+
const dirShort = formatPathTail(dirPath, 3);
4571+
const display = dirShort ? `${dirShort}/${fileNameWithExt}` : fileNameWithExt;
4572+
document.title = `ABCarus — ${display}${dirtyTag}`;
4573+
}
45724574

45734575
function buildTuneMetaLabel(metadata) {
45744576
if (!metadata) return "Untitled";
@@ -11454,6 +11456,9 @@ function initEditor() {
1145411456
]);
1145511457
const updateListener = EditorView.updateListener.of((update) => {
1145611458
if (update.docChanged) {
11459+
if (!suppressDirty && !payloadMode && !currentDoc) {
11460+
currentDoc = createBlankDocument();
11461+
}
1145711462
shouldHandleTypingPreviewChange(update);
1145811463
abRevision += 1;
1145911464
if (abPlan) clearAbPlan({ toast: true });
@@ -11524,6 +11529,7 @@ function initEditor() {
1152411529
doc: DEFAULT_ABC,
1152511530
extensions: [
1152611531
basicSetup,
11532+
createRectSelectionExtension(),
1152711533
abcHighlightCompartment.of([abcHighlight]),
1152811534
abcDiagnosticsCompartment.of([
1152911535
measureErrorPlugin,
@@ -11768,6 +11774,7 @@ function initHeaderEditor() {
1176811774
doc: "",
1176911775
extensions: [
1177011776
basicSetup,
11777+
createRectSelectionExtension(),
1177111778
abcHighlight,
1177211779
keymap.of([{ key: "Mod-/", run: toggleLineComments }]),
1177311780
updateListener,
@@ -11838,8 +11845,8 @@ function setActiveTuneText(text, metadata, options = {}) {
1183811845
activeFilePath = null;
1183911846
isNewTuneDraft = false;
1184011847
refreshHeaderLayers().catch(() => {});
11841-
setTuneMetaText("Untitled");
11842-
setFileNameMeta("Untitled");
11848+
setTuneMetaText(UNTITLED_UNSAVED_LABEL);
11849+
setFileNameMeta(UNTITLED_UNSAVED_LABEL);
1184311850
if (currentDoc) {
1184411851
currentDoc.path = null;
1184511852
currentDoc.content = text || "";
@@ -17595,8 +17602,8 @@ function showEmptyState() {
1759517602
activeFilePath = null;
1759617603
clearSaveSession();
1759717604
headerDirty = false;
17598-
setTuneMetaText("Untitled");
17599-
setFileNameMeta("Untitled");
17605+
setTuneMetaText(UNTITLED_UNSAVED_LABEL);
17606+
setFileNameMeta(UNTITLED_UNSAVED_LABEL);
1760017607
clearErrors();
1760117608
setStatus("Ready");
1760217609
updateFileHeaderPanel();
@@ -19639,10 +19646,9 @@ async function confirmAbandonIfDirty(contextLabel) {
1963919646
const choice = await confirmUnsavedChanges(contextLabel);
1964019647
if (choice === "cancel") return false;
1964119648
if (choice === "dont_save") {
19642-
if (hdrDirty) {
19643-
showToast("Header changes are unsaved. Save or cancel.", 2600);
19644-
return false;
19645-
}
19649+
// Explicit discard path: user chose not to save.
19650+
headerDirty = false;
19651+
updateHeaderStateUI();
1964619652
if (tuneDirty) {
1964719653
await discardWorkingCopyChangesForActiveFile();
1964819654
}
@@ -21825,45 +21831,9 @@ async function requestQuitApplication() {
2182521831
}
2182621832

2182721833
async function fileClose() {
21828-
if (abandonFlowInProgress) return;
21829-
abandonFlowInProgress = true;
21830-
const currentPath = getCurrentNavFilePath();
21831-
try {
21832-
const ok = await confirmAbandonIfDirty("closing this file");
21833-
if (!ok) return;
21834-
21835-
// Close the working copy session (best-effort).
21836-
try {
21837-
if (window.api && typeof window.api.closeWorkingCopy === "function") {
21838-
await window.api.closeWorkingCopy();
21839-
}
21840-
} catch {}
21841-
21842-
clearCurrentDocument();
21843-
setActiveTuneText("", null);
21844-
setDirtyIndicator(false);
21845-
activeFilePath = null;
21846-
21847-
// Activate previous file in navigation history, if available; otherwise keep empty state.
21848-
if (!currentPath) return;
21849-
for (let i = navFileHistory.length - 1; i >= 0; i -= 1) {
21850-
const candidate = navFileHistory[i];
21851-
if (!candidate) continue;
21852-
if (pathsEqual(candidate, currentPath)) continue;
21853-
try {
21854-
if (typeof window.api?.fileExists === "function") {
21855-
const exists = await window.api.fileExists(candidate);
21856-
if (!exists) continue;
21857-
}
21858-
} catch {}
21859-
try {
21860-
const res = await loadLibraryFileIntoEditor(candidate);
21861-
if (res && res.ok) return;
21862-
} catch {}
21863-
}
21864-
} finally {
21865-
abandonFlowInProgress = false;
21866-
}
21834+
// Unified close behavior: close current file to empty state.
21835+
// Keep this wrapper for toolbar call sites.
21836+
await requestCloseDocument();
2186721837
}
2186821838

2186921839
async function exportMusicXml() {
@@ -30296,3 +30266,17 @@ async function maybeRunDevAutoscrollDemo() {
3029630266
}
3029730267

3029830268
maybeRunDevAutoscrollDemo().catch(() => {});
30269+
function createRectSelectionExtension() {
30270+
return rectangularSelection({
30271+
// Linux WMs often reserve Alt+drag for window move/resize.
30272+
// Keep Alt+drag where available, and provide Ctrl+Shift+drag as a reliable fallback.
30273+
eventFilter: (event) => Boolean(
30274+
event
30275+
&& event.button === 0
30276+
&& (
30277+
event.altKey
30278+
|| (event.ctrlKey && event.shiftKey)
30279+
)
30280+
),
30281+
});
30282+
}

src/renderer/settings.js

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,11 +85,22 @@ import {
8585
EditorState,
8686
basicSetup,
8787
indentUnit,
88+
rectangularSelection,
8889
} from "../../third_party/codemirror/cm.js";
8990
import { createSettingsStore } from "./settings_store.js";
9091

9192
const ZOOM_STEP = 0.1;
9293
const SETTINGS_UI_STATE_KEY = "abcarus.settings.uiState.v1";
94+
const rectSelectionExt = rectangularSelection({
95+
eventFilter: (event) => Boolean(
96+
event
97+
&& event.button === 0
98+
&& (
99+
event.altKey
100+
|| (event.ctrlKey && event.shiftKey)
101+
)
102+
),
103+
});
93104
const SETTINGS_SECTION_HINTS = {
94105
general: "General application settings.",
95106
playback: "Playback behavior and visuals.",
@@ -1544,6 +1555,7 @@ export function initSettings(api) {
15441555
doc: "",
15451556
extensions: [
15461557
basicSetup,
1558+
rectSelectionExt,
15471559
updateListener,
15481560
EditorState.tabSize.of(2),
15491561
indentUnit.of(" "),

0 commit comments

Comments
 (0)