mirror of
https://github.com/ksyasuda/SubMiner.git
synced 2026-05-27 00:55:16 -07:00
rename config window to settings and update CLI entry points
- Replace `--config`/`subminer config` (no action) with `--settings`/`subminer settings` - `subminer config` now requires an explicit action (`path` or `show`) - `--settings` previously opened Yomitan; replaced by `--yomitan` - Linux tray update installs AppImage via electron-updater instead of manual flow - macOS update dialog activation and curl-fetch routing fixes - Delete stale compiled artifacts (main.js, app-updater.js)
This commit is contained in:
@@ -7,22 +7,22 @@
|
||||
http-equiv="Content-Security-Policy"
|
||||
content="default-src 'self'; script-src 'self'; style-src 'self';"
|
||||
/>
|
||||
<title>SubMiner Configuration</title>
|
||||
<title>SubMiner Settings</title>
|
||||
<link rel="stylesheet" href="style.css" />
|
||||
</head>
|
||||
<body>
|
||||
<main id="app" class="settings-shell">
|
||||
<aside class="settings-nav" aria-label="Configuration categories">
|
||||
<aside class="settings-nav" aria-label="Settings categories">
|
||||
<div class="brand-block">
|
||||
<div class="brand-title">SubMiner</div>
|
||||
<div class="brand-subtitle">Configuration</div>
|
||||
<div class="brand-subtitle">Settings</div>
|
||||
</div>
|
||||
<nav id="categoryNav" class="category-nav"></nav>
|
||||
</aside>
|
||||
<section class="settings-main">
|
||||
<header class="settings-toolbar">
|
||||
<div class="toolbar-title-block">
|
||||
<h1 id="categoryTitle">Configuration</h1>
|
||||
<h1 id="categoryTitle">Settings</h1>
|
||||
<div id="categoryMeta" class="toolbar-meta"></div>
|
||||
</div>
|
||||
<div class="toolbar-actions">
|
||||
|
||||
@@ -511,8 +511,12 @@ export function renderKnownWordsDecksInput(
|
||||
void loadAnkiDeckFieldNames(deckName, draftUrl);
|
||||
}
|
||||
const row = createElement('div', 'deck-field-row');
|
||||
const header = createElement('div', 'deck-field-row-header');
|
||||
const usedDeckNames = new Set(Object.keys(currentDecks));
|
||||
const deckSelect = createElement('select', 'config-input') as HTMLSelectElement;
|
||||
const deckSelect = createElement(
|
||||
'select',
|
||||
'config-input deck-field-row-name',
|
||||
) as HTMLSelectElement;
|
||||
for (const candidateDeck of uniqueSorted([...deckNames, deckName])) {
|
||||
if (candidateDeck !== deckName && usedDeckNames.has(candidateDeck)) continue;
|
||||
addOption(deckSelect, candidateDeck);
|
||||
@@ -534,7 +538,6 @@ export function renderKnownWordsDecksInput(
|
||||
|
||||
const availableFields = deckName ? (state.deckFieldNames.get(deckName) ?? []) : [];
|
||||
const fieldNames = uniqueSorted([...availableFields, ...selectedFields]);
|
||||
const fieldsWrap = createElement('div', 'deck-field-fields');
|
||||
const fieldActions = createElement('div', 'deck-field-actions');
|
||||
const checkboxList = createElement('div', 'field-checkbox-list');
|
||||
|
||||
@@ -569,7 +572,6 @@ export function renderKnownWordsDecksInput(
|
||||
});
|
||||
|
||||
fieldActions.append(selectAllButton, clearButton);
|
||||
fieldsWrap.append(fieldActions, checkboxList);
|
||||
|
||||
if (state.deckFieldNamesLoading.has(deckName)) {
|
||||
const hint = createElement('div', 'control-hint');
|
||||
@@ -609,7 +611,8 @@ export function renderKnownWordsDecksInput(
|
||||
requestRender();
|
||||
});
|
||||
|
||||
row.append(deckSelect, fieldsWrap, removeButton);
|
||||
header.append(deckSelect, removeButton);
|
||||
row.append(header, fieldActions, checkboxList);
|
||||
const error = state.deckFieldNamesErrors.get(deckName);
|
||||
if (error) {
|
||||
const hint = createElement('div', 'control-hint error');
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import type { ConfigSettingsField, ConfigSettingsSnapshotValue } from '../types/settings';
|
||||
import { toConfigDraftValue, toSettingsDisplayValue } from './settings-model';
|
||||
import { parseOptionalNumberInputValue } from './input-values';
|
||||
import {
|
||||
configureAnkiControls,
|
||||
@@ -143,7 +144,7 @@ export function renderControl(
|
||||
field: ConfigSettingsField,
|
||||
context: SettingsControlContext,
|
||||
): HTMLElement {
|
||||
const value = context.valueForField(field);
|
||||
const value = toSettingsDisplayValue(field.configPath, context.valueForField(field));
|
||||
|
||||
if (field.control === 'keyboard-shortcut') {
|
||||
return renderKeyboardInput(context, field, 'accelerator');
|
||||
@@ -199,7 +200,7 @@ export function renderControl(
|
||||
if (next.ok) {
|
||||
input.classList.remove('invalid');
|
||||
context.setFieldError(field.configPath, null);
|
||||
context.updateDraft(field.configPath, next.value);
|
||||
context.updateDraft(field.configPath, toConfigDraftValue(field.configPath, next.value));
|
||||
} else {
|
||||
input.classList.add('invalid');
|
||||
context.setFieldError(field.configPath, 'Invalid number');
|
||||
|
||||
@@ -6,6 +6,8 @@ import {
|
||||
setDraftValue,
|
||||
resetDraftPath,
|
||||
getDirtyOperations,
|
||||
toConfigDraftValue,
|
||||
toSettingsDisplayValue,
|
||||
} from './settings-model';
|
||||
import type { ConfigSettingsField } from '../types/settings';
|
||||
|
||||
@@ -16,7 +18,7 @@ const fields: ConfigSettingsField[] = [
|
||||
description: 'Pause while hovering subtitles.',
|
||||
configPath: 'subtitleStyle.autoPauseVideoOnHover',
|
||||
category: 'behavior',
|
||||
section: 'Playback Pause Behavior',
|
||||
section: 'Playback Behavior',
|
||||
control: 'boolean',
|
||||
defaultValue: true,
|
||||
restartBehavior: 'hot-reload',
|
||||
@@ -147,3 +149,10 @@ test('settings draft emits reset operations for css-editor-owned legacy style pa
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
test('discord presence update interval displays seconds while saving milliseconds', () => {
|
||||
const path = 'discordPresence.updateIntervalMs';
|
||||
|
||||
assert.equal(toSettingsDisplayValue(path, 3000), 3);
|
||||
assert.equal(toConfigDraftValue(path, 2.5), 2500);
|
||||
});
|
||||
|
||||
@@ -71,6 +71,26 @@ export function createSettingsDraft(
|
||||
};
|
||||
}
|
||||
|
||||
export function toSettingsDisplayValue(
|
||||
path: string,
|
||||
value: ConfigSettingsSnapshotValue,
|
||||
): ConfigSettingsSnapshotValue {
|
||||
if (path === 'discordPresence.updateIntervalMs' && typeof value === 'number') {
|
||||
return value / 1000;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
export function toConfigDraftValue(
|
||||
path: string,
|
||||
value: ConfigSettingsSnapshotValue,
|
||||
): ConfigSettingsSnapshotValue {
|
||||
if (path === 'discordPresence.updateIntervalMs' && typeof value === 'number') {
|
||||
return Math.round(value * 1000);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
export function setDraftValue(
|
||||
draft: SettingsDraft,
|
||||
path: string,
|
||||
|
||||
+16
-9
@@ -615,17 +615,25 @@ code {
|
||||
}
|
||||
|
||||
.deck-field-row {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(140px, 0.75fr) minmax(220px, 1.25fr) auto;
|
||||
gap: 8px;
|
||||
align-items: start;
|
||||
}
|
||||
|
||||
.deck-field-fields {
|
||||
display: flex;
|
||||
min-width: 0;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
gap: 8px;
|
||||
padding: 12px;
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 8px;
|
||||
background: rgba(24, 25, 38, 0.4);
|
||||
}
|
||||
|
||||
.deck-field-row-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.deck-field-row-name {
|
||||
min-width: 0;
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
|
||||
.deck-field-actions {
|
||||
@@ -823,7 +831,6 @@ code {
|
||||
.settings-toolbar,
|
||||
.field-row,
|
||||
.field-control,
|
||||
.deck-field-row,
|
||||
.keybinding-row {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
Reference in New Issue
Block a user