Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions src-tauri/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -121,3 +121,10 @@ pub async fn config_delete_connection(id: String) -> Result<(), String> {
all.retain(|c| c.id != id);
save_connections(&all)
}

#[tauri::command]
pub async fn config_reorder_connections(ids: Vec<String>) -> Result<(), String> {
let mut all = load_connections()?;
all.sort_by_key(|c| ids.iter().position(|id| id == &c.id).unwrap_or(usize::MAX));
save_connections(&all)
}
1 change: 1 addition & 0 deletions src-tauri/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ pub fn run() {
config::config_list_connections,
config::config_save_connection,
config::config_delete_connection,
config::config_reorder_connections,
logger::logger_open_dir,
logger::logger_dir_path,
logger::logs_list,
Expand Down
57 changes: 55 additions & 2 deletions src/components/ButtonEditor.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { createSignal, For, Show } from "solid-js";
import { createSignal, For, onCleanup, onMount, Show } from "solid-js";
import { CloseX } from "./CloseX";
import {
buttons,
loadButtons,
moveButton,
newButtonId,
removeButton,
saveButton,
Expand Down Expand Up @@ -30,6 +31,22 @@ export function ButtonEditor(props: Props) {

loadButtons();

// ESC closes the dialog (or backs out of the edit form).
onMount(() => {
const onKey = (e: KeyboardEvent) => {
if (e.key !== "Escape") return;
if (editing()) {
e.preventDefault();
setEditing(null);
} else {
e.preventDefault();
props.onClose();
}
};
document.addEventListener("keydown", onKey);
onCleanup(() => document.removeEventListener("keydown", onKey));
});

function startEdit(b: CommandButton) {
setEditing({ ...b });
}
Expand Down Expand Up @@ -57,8 +74,26 @@ export function ButtonEditor(props: Props) {
<Show when={!editing()}>
<Show when={buttons().length > 0} fallback={<div style={{ opacity: 0.6, padding: "20px", "text-align": "center" }}>No buttons. Click + New.</div>}>
<For each={buttons()}>
{(b) => (
{(b, i) => (
<div style={row}>
<div style={reorderCol}>
<button
onClick={() => moveButton(b.id, -1)}
disabled={i() === 0}
style={{ ...arrowBtn, opacity: i() === 0 ? 0.3 : 0.7 }}
title="Move up"
>
</button>
<button
onClick={() => moveButton(b.id, 1)}
disabled={i() === buttons().length - 1}
style={{ ...arrowBtn, opacity: i() === buttons().length - 1 ? 0.3 : 0.7 }}
title="Move down"
>
</button>
</div>
<div style={{ flex: 1, "min-width": 0 }}>
<div style={{ "font-weight": 600 }}>
{b.icon ? `${b.icon} ` : ""}
Expand Down Expand Up @@ -173,3 +208,21 @@ const btn = {
cursor: "pointer",
"font-weight": 600,
} as const;

const reorderCol = {
display: "flex",
"flex-direction": "column",
gap: "1px",
"margin-right": "4px",
} as const;

const arrowBtn = {
background: "transparent",
color: "#cdd6f4",
border: "1px solid #45475a",
"border-radius": "4px",
padding: "1px 6px",
"font-size": "9px",
"line-height": "1",
cursor: "pointer",
} as const;
62 changes: 60 additions & 2 deletions src/components/ConnectionDialog.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import { createSignal, For, Show } from "solid-js";
import { createSignal, For, onCleanup, onMount, Show } from "solid-js";
import { C, overlayStyle as baseOverlay, dialogStyle as baseDialog, inputStyle, btnPrimary, btnSecondary, btnDanger } from "../theme";
import { CloseX } from "./CloseX";
import {
connections,
defaultLocalShell,
deleteConnection,
moveConnection,
newConnectionId,
upsertConnection,
} from "../stores/connections";
Expand Down Expand Up @@ -32,6 +33,26 @@ export function ConnectionDialog(props: Props) {
const [editing, setEditing] = createSignal<Connection | null>(null);
const [pwPrompt, setPwPrompt] = createSignal<{ conn: Connection; pw: string } | null>(null);

// ESC closes the dialog. When a sub-state (edit form / password prompt)
// is open, ESC backs out of that state first instead of closing outright.
onMount(() => {
const onKey = (e: KeyboardEvent) => {
if (e.key !== "Escape") return;
if (editing()) {
e.preventDefault();
setEditing(null);
} else if (pwPrompt()) {
e.preventDefault();
setPwPrompt(null);
} else {
e.preventDefault();
props.onClose();
}
};
document.addEventListener("keydown", onKey);
onCleanup(() => document.removeEventListener("keydown", onKey));
});

function startEdit(c: Connection) {
setEditing({ ...c });
}
Expand Down Expand Up @@ -75,8 +96,26 @@ export function ConnectionDialog(props: Props) {
fallback={<div style={{ opacity: 0.6, padding: "20px", "text-align": "center" }}>No saved connections. Click + New.</div>}
>
<For each={connections()}>
{(c) => (
{(c, i) => (
<div style={rowStyle}>
<div style={reorderColStyle}>
<button
onClick={() => moveConnection(c.id, -1)}
disabled={i() === 0}
style={{ ...arrowBtn, opacity: i() === 0 ? 0.3 : 0.7 }}
title="Move up"
>
</button>
<button
onClick={() => moveConnection(c.id, 1)}
disabled={i() === connections().length - 1}
style={{ ...arrowBtn, opacity: i() === connections().length - 1 ? 0.3 : 0.7 }}
title="Move down"
>
</button>
</div>
<div style={{ flex: 1, "min-width": 0 }}>
<div style={{ "font-weight": 600 }}>{c.name}</div>
<div style={{ "font-size": "12px", opacity: 0.7 }}>
Expand Down Expand Up @@ -208,6 +247,25 @@ const rowStyle = {
"border-radius": "8px",
background: C.bg3,
"margin-bottom": "6px",
"user-select": "none",
} as const;

const reorderColStyle = {
display: "flex",
"flex-direction": "column",
gap: "1px",
"margin-right": "4px",
} as const;

const arrowBtn = {
background: "transparent",
color: C.text2,
border: `1px solid ${C.border}`,
"border-radius": "4px",
padding: "1px 6px",
"font-size": "9px",
"line-height": "1",
cursor: "pointer",
} as const;

const input = inputStyle;
3 changes: 3 additions & 0 deletions src/ipc/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,9 @@ export const api = {
deleteConnection: (id: string) =>
invoke<void>("config_delete_connection", { id }),

reorderConnections: (ids: string[]) =>
invoke<void>("config_reorder_connections", { ids }),

// Logging
loggerOpenDir: () => invoke<void>("logger_open_dir"),
loggerDirPath: () => invoke<string>("logger_dir_path"),
Expand Down
15 changes: 15 additions & 0 deletions src/stores/buttons.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,21 @@ export async function reorderButtons(ids: string[]) {
await loadButtons();
}

export async function moveButton(id: string, delta: -1 | 1) {
const arr = [...buttons()];
const idx = arr.findIndex((b) => b.id === id);
if (idx < 0) return;
const newIdx = idx + delta;
if (newIdx < 0 || newIdx >= arr.length) return;
const [moved] = arr.splice(idx, 1);
arr.splice(newIdx, 0, moved);
setButtons(arr);
await api.buttonsReorder(arr.map((b) => b.id)).catch((e) => {
console.error("moveButton persist failed", e);
loadButtons();
});
}

export function newButtonId(): string {
return `btn-${Date.now()}-${Math.random().toString(36).slice(2, 6)}`;
}
30 changes: 30 additions & 0 deletions src/stores/connections.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,36 @@ export async function deleteConnection(id: string) {
await loadConnections();
}

export async function reorderConnections(sourceId: string, targetId: string | null) {
if (sourceId === targetId) return;
const arr = [...connections()];
const fromIdx = arr.findIndex((c) => c.id === sourceId);
if (fromIdx < 0) return;
const [moved] = arr.splice(fromIdx, 1);
if (targetId === null) {
arr.push(moved);
} else {
const toIdx = arr.findIndex((c) => c.id === targetId);
if (toIdx < 0) arr.push(moved);
else arr.splice(toIdx, 0, moved);
}
setConnections(arr);
await api.reorderConnections(arr.map((c) => c.id)).catch((e) => {
console.error("reorderConnections persist failed", e);
loadConnections();
});
}

export async function moveConnection(id: string, delta: -1 | 1) {
const arr = connections();
const idx = arr.findIndex((c) => c.id === id);
if (idx < 0) return;
const newIdx = idx + delta;
if (newIdx < 0 || newIdx >= arr.length) return;
const targetId = delta < 0 ? arr[newIdx].id : (newIdx + 1 < arr.length ? arr[newIdx + 1].id : null);
await reorderConnections(id, targetId);
}

export function newConnectionId(): string {
return `conn-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
}
Expand Down
Loading