Return from files to explorer

This commit is contained in:
iris committed 2026-09-09 22:48:52 -04:00
1 parent cbdd8493ed
commit 4c15150338
2 files changed
+26 -14

No files matched your search

+5 -5
View File
@@ -214,11 +214,11 @@ be lost. The explorer draws over the session, which deliberately has no
`FilesScreen` is composed **on top of** the session in the same `Box`, and `FilesScreen` is composed **on top of** the session in the same `Box`, and
the session stays composed under it: its event stream keeps flowing, its the session stays composed under it: its event stream keeps flowing, its
scroll position and draft stay where they were, and returning from a file scroll position and draft stay where they were, and returning from a file
costs nothing. Back — the button and the platform gesture — clears `files` costs nothing. Back — the button and the platform gesture — returns from an
and returns to the session from anywhere in the explorer. Directory navigation open file to its containing directory, then clears `files` and returns to the
stays in the listing: `..` is an explicit row rather than a hidden second session. Directory navigation stays in the listing: `..` is an explicit row
meaning for Back. An editor with unsaved changes asks before closing. "Back rather than a hidden second meaning for Back. An editor with unsaved changes
returns; it does not exit." asks before returning to the directory. "Back returns; it does not exit."
Rejected: a `Screen.Files` beside `Screen.Session`. Every route back from a Rejected: a `Screen.Files` beside `Screen.Session`. Every route back from a
leaf screen goes to Main today, and a session disposed and re-created on each leaf screen goes to Main today, and a session disposed and re-created on each
@@ -54,15 +54,16 @@ data class FilesTarget(val setup: String, val setupName: String, val start: Stri
private sealed class Spot(val path: String) { private sealed class Spot(val path: String) {
class Dir(path: String) : Spot(path) class Dir(path: String) : Spot(path)
class Doc(path: String) : Spot(path) class Doc(path: String, val directory: Dir) : Spot(path)
} }
/** /**
* The files on the machine a session runs on: browse them, read one, change one. * The files on the machine a session runs on: browse them, read one, change one.
* *
* Drawn **over** the session rather than instead of it (see [AppRoot]), so its event stream keeps * Drawn **over** the session rather than instead of it (see [AppRoot]), so its event stream keeps
* flowing and coming back from a file costs nothing. Back always closes the explorer and returns to * flowing and coming back from a file costs nothing. Back from a file returns to its directory;
* that session; directory navigation stays inside the listing, where its `..` row is explicit. * back from a directory closes the explorer and returns to the session. Directory navigation stays
* inside the listing, where its `..` row is explicit.
* *
* Every directory that has been visited is kept for as long as this is open; the refresh glyph is * Every directory that has been visited is kept for as long as this is open; the refresh glyph is
* how one gets asked again on purpose, and creating something refetches the directory it was * how one gets asked again on purpose, and creating something refetches the directory it was
@@ -88,7 +89,11 @@ fun FilesScreen(settings: ServerSettings, target: FilesTarget, onClose: () -> Un
} }
fun back() { fun back() {
if (editing && dirty) askUnsaved = true else onClose() when {
editing && dirty -> askUnsaved = true
here is Spot.Doc -> go((here as Spot.Doc).directory)
else -> onClose()
}
} }
suspend fun load(path: String, again: Boolean) { suspend fun load(path: String, again: Boolean) {
@@ -140,7 +145,7 @@ fun FilesScreen(settings: ServerSettings, target: FilesTarget, onClose: () -> Un
) )
} }
LaunchedEffect(spot.path) { load(spot.path, again = false) } LaunchedEffect(spot.path) { load(spot.path, again = false) }
DirectoryBody(state, onOpen = ::go) DirectoryBody(state, directory = spot, onOpen = ::go)
} }
is Spot.Doc -> is Spot.Doc ->
DocPane( DocPane(
@@ -161,7 +166,8 @@ fun FilesScreen(settings: ServerSettings, target: FilesTarget, onClose: () -> Un
UnsavedDialog( UnsavedDialog(
onDiscard = { onDiscard = {
askUnsaved = false askUnsaved = false
onClose() val spot = here
if (spot is Spot.Doc) go(spot.directory) else onClose()
}, },
onCancel = { askUnsaved = false }, onCancel = { askUnsaved = false },
) )
@@ -183,7 +189,7 @@ fun FilesScreen(settings: ServerSettings, target: FilesTarget, onClose: () -> Un
load(dir.path, again = true) load(dir.path, again = true)
// A new file has nothing to look at, so it opens where it can be filled in. // A new file has nothing to look at, so it opens where it can be filled in.
if (!isDirectory) { if (!isDirectory) {
go(Spot.Doc(path)) go(Spot.Doc(path, dir))
editing = true editing = true
} }
} }
@@ -235,7 +241,11 @@ private fun FilesHeader(
* looks like a right one. * looks like a right one.
*/ */
@Composable @Composable
private fun ColumnScope.DirectoryBody(state: LoadState<Listing>, onOpen: (Spot) -> Unit) { private fun ColumnScope.DirectoryBody(
state: LoadState<Listing>,
directory: Spot.Dir,
onOpen: (Spot) -> Unit,
) {
when (state) { when (state) {
is LoadState.Loading -> CircularProgressIndicator(Modifier.padding(16.dp)) is LoadState.Loading -> CircularProgressIndicator(Modifier.padding(16.dp))
is LoadState.Error -> is LoadState.Error ->
@@ -276,7 +286,9 @@ private fun ColumnScope.DirectoryBody(state: LoadState<Listing>, onOpen: (Spot)
name = entry.name, name = entry.name,
trailing = trailingOf(entry), trailing = trailingOf(entry),
onClick = { onClick = {
onOpen(if (entry.isDirectory) Spot.Dir(path) else Spot.Doc(path)) onOpen(
if (entry.isDirectory) Spot.Dir(path) else Spot.Doc(path, directory)
)
}, },
) )
} }