feat: add onyx dark theme support
Add onyx theme as a new dark theme option. Register it in the dark themes set, add the theme option to the settings dropdown, and define complete color variables and scrollbar styling for the onyx theme. Also improve sync error handling to gracefully skip upload and conflict resolution actions when files are missing, preventing sync failures due to files moved or deleted between action computation and execution.
This commit is contained in:
parent
c40cf15f08
commit
973d575b51
|
|
@ -139,6 +139,33 @@ body {
|
||||||
--color-danger: #ff5555;
|
--color-danger: #ff5555;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[data-theme="onyx"] {
|
||||||
|
--color-primary: #ffffff;
|
||||||
|
--color-primary-hover: #e0e0e0;
|
||||||
|
--color-surface-light: #0a0a0a;
|
||||||
|
--color-surface-dark: #0a0a0a;
|
||||||
|
--color-card-light: #161616;
|
||||||
|
--color-card-dark: #161616;
|
||||||
|
--color-text-light: #c4a95a;
|
||||||
|
--color-text-dark: #c4a95a;
|
||||||
|
--color-text-secondary-light: #917a3e;
|
||||||
|
--color-text-secondary-dark: #917a3e;
|
||||||
|
--color-border-light: #2a2418;
|
||||||
|
--color-border-dark: #2a2418;
|
||||||
|
--color-danger: #ef4444;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="onyx"] * {
|
||||||
|
scrollbar-color: #917a3e transparent;
|
||||||
|
}
|
||||||
|
[data-theme="onyx"] ::-webkit-scrollbar-thumb {
|
||||||
|
background: #917a3e;
|
||||||
|
}
|
||||||
|
[data-theme="onyx"] select option {
|
||||||
|
background-color: #0a0a0a;
|
||||||
|
color: #c4a95a;
|
||||||
|
}
|
||||||
|
|
||||||
[data-theme="solarized"] {
|
[data-theme="solarized"] {
|
||||||
--color-primary: #268bd2;
|
--color-primary: #268bd2;
|
||||||
--color-primary-hover: #1e7ac0;
|
--color-primary-hover: #1e7ac0;
|
||||||
|
|
|
||||||
|
|
@ -259,6 +259,7 @@
|
||||||
<option value="nord">Nord</option>
|
<option value="nord">Nord</option>
|
||||||
<option value="dracula">Dracula</option>
|
<option value="dracula">Dracula</option>
|
||||||
<option value="solarized">Solarized Dark</option>
|
<option value="solarized">Solarized Dark</option>
|
||||||
|
<option value="onyx">Onyx</option>
|
||||||
</select>
|
</select>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -67,7 +67,7 @@ let hasWorkspace = $derived(
|
||||||
Object.keys(config.workspaces).length > 0,
|
Object.keys(config.workspaces).length > 0,
|
||||||
);
|
);
|
||||||
|
|
||||||
const DARK_THEMES = new Set(["dark", "nord", "dracula", "solarized"]);
|
const DARK_THEMES = new Set(["dark", "nord", "dracula", "solarized", "onyx"]);
|
||||||
let currentTheme = $derived(
|
let currentTheme = $derived(
|
||||||
config?.current_workspace
|
config?.current_workspace
|
||||||
? config.workspaces[config.current_workspace]?.theme ?? null
|
? config.workspaces[config.current_workspace]?.theme ?? null
|
||||||
|
|
|
||||||
|
|
@ -688,7 +688,18 @@ async fn execute_action(
|
||||||
match action {
|
match action {
|
||||||
SyncAction::Upload { path } => {
|
SyncAction::Upload { path } => {
|
||||||
let local_path = workspace_path.join(path.replace('/', std::path::MAIN_SEPARATOR_STR));
|
let local_path = workspace_path.join(path.replace('/', std::path::MAIN_SEPARATOR_STR));
|
||||||
let data = std::fs::read(&local_path)?;
|
let data = match std::fs::read(&local_path) {
|
||||||
|
Ok(d) => d,
|
||||||
|
Err(e) if e.kind() == std::io::ErrorKind::NotFound => {
|
||||||
|
// File was moved or deleted since the action was computed (e.g. task
|
||||||
|
// moved between lists). Drop the stale upload and clean up sync state
|
||||||
|
// so the next cycle can re-evaluate from a clean baseline.
|
||||||
|
report(&format!(" - Skipping upload for missing file {}", path));
|
||||||
|
sync_state.files.remove(path.as_str());
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
Err(e) => return Err(e.into()),
|
||||||
|
};
|
||||||
let checksum = compute_checksum(&data);
|
let checksum = compute_checksum(&data);
|
||||||
|
|
||||||
if let Some(parent) = path_parent(path) {
|
if let Some(parent) = path_parent(path) {
|
||||||
|
|
@ -707,7 +718,16 @@ async fn execute_action(
|
||||||
|
|
||||||
SyncAction::Conflict { path } => {
|
SyncAction::Conflict { path } => {
|
||||||
let local_path = workspace_path.join(path.replace('/', std::path::MAIN_SEPARATOR_STR));
|
let local_path = workspace_path.join(path.replace('/', std::path::MAIN_SEPARATOR_STR));
|
||||||
let local_data = std::fs::read(&local_path)?;
|
let local_data = match std::fs::read(&local_path) {
|
||||||
|
Ok(d) => d,
|
||||||
|
Err(e) if e.kind() == std::io::ErrorKind::NotFound => {
|
||||||
|
// Local file gone — treat as remote-wins: download it on next cycle.
|
||||||
|
report(&format!(" - Skipping conflict for missing local file {}", path));
|
||||||
|
sync_state.files.remove(path.as_str());
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
Err(e) => return Err(e.into()),
|
||||||
|
};
|
||||||
let local_checksum = compute_checksum(&local_data);
|
let local_checksum = compute_checksum(&local_data);
|
||||||
|
|
||||||
let remote_data = client.get_file(path).await?;
|
let remote_data = client.get_file(path).await?;
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue