feat(desktop): single-pane workspaces store nav bar screen list
Change Workspace.singlePaneScreen (single string) to singlePaneScreens (list of typeKey strings). First screen is the default. On workspace switch, loads the screen list into PinnedNavBarState and navigates to the first screen. - Add SinglePaneScreensEditor to workspace editor dialog - Add PinnedNavBarState.loadFromList() for workspace-driven nav - Backward compat: load old "singlePaneScreen" format as single-item list - Cmd+Shift+S captures current deck columns as singlePaneScreens Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -267,6 +267,12 @@ fun main() {
|
||||
iconName = "Star",
|
||||
layoutMode = layoutMode,
|
||||
columns = columns,
|
||||
singlePaneScreens =
|
||||
if (layoutMode == LayoutMode.SINGLE_PANE) {
|
||||
columns.map { it.typeKey }
|
||||
} else {
|
||||
emptyList()
|
||||
},
|
||||
)
|
||||
workspaceManager.addWorkspace(ws)
|
||||
}
|
||||
@@ -789,12 +795,17 @@ fun App(
|
||||
}
|
||||
|
||||
LayoutMode.SINGLE_PANE -> {
|
||||
val screenKey =
|
||||
ws.singlePaneScreen
|
||||
?: ws.columns.firstOrNull()?.typeKey
|
||||
?: "home"
|
||||
val type = DeckState.parseColumnTypeFromKey(screenKey)
|
||||
if (type != null) singlePaneState.navigate(type)
|
||||
// Set nav bar screens from workspace
|
||||
if (ws.singlePaneScreens.isNotEmpty()) {
|
||||
val screens =
|
||||
ws.singlePaneScreens.mapNotNull {
|
||||
DeckState.parseColumnTypeFromKey(it)
|
||||
}
|
||||
if (screens.isNotEmpty()) {
|
||||
pinnedNavBarState.loadFromList(screens)
|
||||
singlePaneState.navigate(screens.first())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
+50
-24
@@ -55,6 +55,7 @@ import androidx.compose.material.icons.filled.Person
|
||||
import androidx.compose.material.icons.filled.PushPin
|
||||
import androidx.compose.material.icons.filled.Search
|
||||
import androidx.compose.material.icons.filled.SportsEsports
|
||||
import androidx.compose.material.icons.filled.Star
|
||||
import androidx.compose.material3.AlertDialog
|
||||
import androidx.compose.material3.Button
|
||||
import androidx.compose.material3.DropdownMenu
|
||||
@@ -883,7 +884,9 @@ private fun WorkspaceEditorDialog(
|
||||
var columns by remember {
|
||||
mutableStateOf(initial?.columns ?: listOf(Workspace.WorkspaceColumn("home")))
|
||||
}
|
||||
var singlePaneScreen by remember { mutableStateOf(initial?.singlePaneScreen) }
|
||||
var singlePaneScreens by remember {
|
||||
mutableStateOf(initial?.singlePaneScreens ?: listOf("home"))
|
||||
}
|
||||
|
||||
AlertDialog(
|
||||
onDismissRequest = onDismiss,
|
||||
@@ -941,11 +944,11 @@ private fun WorkspaceEditorDialog(
|
||||
if (layoutMode == LayoutMode.DECK) {
|
||||
DeckColumnEditor(columns = columns, onColumnsChange = { columns = it })
|
||||
}
|
||||
// Single Pane: screen picker
|
||||
// Single Pane: nav bar screen selector (first = default)
|
||||
if (layoutMode == LayoutMode.SINGLE_PANE) {
|
||||
SinglePaneScreenPicker(
|
||||
selected = singlePaneScreen,
|
||||
onSelect = { singlePaneScreen = it },
|
||||
SinglePaneScreensEditor(
|
||||
screens = singlePaneScreens,
|
||||
onScreensChange = { singlePaneScreens = it },
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -963,7 +966,7 @@ private fun WorkspaceEditorDialog(
|
||||
iconName = iconName,
|
||||
layoutMode = layoutMode,
|
||||
columns = columns,
|
||||
singlePaneScreen = singlePaneScreen,
|
||||
singlePaneScreens = singlePaneScreens,
|
||||
),
|
||||
)
|
||||
},
|
||||
@@ -1021,29 +1024,52 @@ private fun DeckColumnEditor(
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun SinglePaneScreenPicker(
|
||||
selected: String?,
|
||||
onSelect: (String) -> Unit,
|
||||
private fun SinglePaneScreensEditor(
|
||||
screens: List<String>,
|
||||
onScreensChange: (List<String>) -> Unit,
|
||||
) {
|
||||
var expanded by remember { mutableStateOf(false) }
|
||||
val selectedTitle =
|
||||
selected?.let { DeckState.parseColumnTypeFromKey(it)?.title() } ?: "Select screen"
|
||||
|
||||
Column {
|
||||
Text("Screen", style = MaterialTheme.typography.labelMedium)
|
||||
Text("Nav Bar Screens", style = MaterialTheme.typography.labelMedium)
|
||||
Text(
|
||||
"First screen is the default",
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
)
|
||||
Spacer(Modifier.height(4.dp))
|
||||
TextButton(onClick = { expanded = true }) { Text(selectedTitle) }
|
||||
DropdownMenu(expanded = expanded, onDismissRequest = { expanded = false }) {
|
||||
LAUNCHABLE_SCREENS.filter { !it.requiresInput() }.forEach { screen ->
|
||||
DropdownMenuItem(
|
||||
text = { Text(screen.title()) },
|
||||
onClick = {
|
||||
onSelect(screen.typeKey())
|
||||
expanded = false
|
||||
},
|
||||
)
|
||||
screens.forEachIndexed { idx, key ->
|
||||
val displayName = DeckState.parseColumnTypeFromKey(key)?.title() ?: key
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
if (idx == 0) {
|
||||
Icon(
|
||||
Icons.Default.Star,
|
||||
"Default",
|
||||
Modifier.size(16.dp),
|
||||
tint = MaterialTheme.colorScheme.primary,
|
||||
)
|
||||
Spacer(Modifier.width(4.dp))
|
||||
}
|
||||
Text(displayName, Modifier.weight(1f))
|
||||
IconButton(onClick = {
|
||||
onScreensChange(screens.toMutableList().apply { removeAt(idx) })
|
||||
}) { Icon(Icons.Default.Close, "Remove", Modifier.size(16.dp)) }
|
||||
}
|
||||
}
|
||||
// Add screen dropdown
|
||||
var expanded by remember { mutableStateOf(false) }
|
||||
TextButton(onClick = { expanded = true }) { Text("+ Add Screen") }
|
||||
DropdownMenu(expanded = expanded, onDismissRequest = { expanded = false }) {
|
||||
PinnedNavBarState.PINNABLE_SCREENS
|
||||
.filter { screen -> screens.none { it == screen.typeKey() } }
|
||||
.forEach { screen ->
|
||||
DropdownMenuItem(
|
||||
text = { Text(screen.title()) },
|
||||
onClick = {
|
||||
onScreensChange(screens + screen.typeKey())
|
||||
expanded = false
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+5
@@ -67,6 +67,11 @@ class PinnedNavBarState {
|
||||
DesktopPreferences.pinnedNavItems = _pinnedScreens.value.joinToString(",") { it.typeKey() }
|
||||
}
|
||||
|
||||
fun loadFromList(screens: List<DeckColumnType>) {
|
||||
_pinnedScreens.value = screens.ifEmpty { DEFAULT_PINNED }
|
||||
save()
|
||||
}
|
||||
|
||||
fun load() {
|
||||
val raw = DesktopPreferences.pinnedNavItems
|
||||
if (raw.isBlank()) {
|
||||
|
||||
+1
-1
@@ -47,7 +47,7 @@ data class Workspace(
|
||||
val iconName: String,
|
||||
val layoutMode: LayoutMode,
|
||||
val columns: List<WorkspaceColumn>,
|
||||
val singlePaneScreen: String? = null,
|
||||
val singlePaneScreens: List<String> = emptyList(),
|
||||
) {
|
||||
data class WorkspaceColumn(
|
||||
val typeKey: String,
|
||||
|
||||
+10
-3
@@ -124,7 +124,7 @@ class WorkspaceManager(
|
||||
"name" to ws.name,
|
||||
"iconName" to ws.iconName,
|
||||
"layoutMode" to ws.layoutMode.name,
|
||||
"singlePaneScreen" to ws.singlePaneScreen,
|
||||
"singlePaneScreens" to ws.singlePaneScreens,
|
||||
"columns" to
|
||||
ws.columns.map { col ->
|
||||
mapOf(
|
||||
@@ -162,7 +162,14 @@ class WorkspaceManager(
|
||||
} catch (e: Exception) {
|
||||
LayoutMode.DECK
|
||||
}
|
||||
val singlePaneScreen = entry["singlePaneScreen"] as? String
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
val singlePaneScreens =
|
||||
(entry["singlePaneScreens"] as? List<String>) ?: run {
|
||||
// Backward compat: old format had single "singlePaneScreen" string
|
||||
val legacy = entry["singlePaneScreen"] as? String
|
||||
if (legacy != null) listOf(legacy) else emptyList()
|
||||
}
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
val columns =
|
||||
@@ -183,7 +190,7 @@ class WorkspaceManager(
|
||||
iconName = iconName,
|
||||
layoutMode = layoutMode,
|
||||
columns = columns,
|
||||
singlePaneScreen = singlePaneScreen,
|
||||
singlePaneScreens = singlePaneScreens,
|
||||
)
|
||||
} catch (e: Exception) {
|
||||
null
|
||||
|
||||
Reference in New Issue
Block a user