mirror of
https://github.com/ksyasuda/SubMiner.git
synced 2026-04-03 06:12:07 -07:00
Require mpv plugin in first-run setup
This commit is contained in:
@@ -21,7 +21,7 @@ Look up words with Yomitan, export to Anki in one key, track your immersion —
|
|||||||
|
|
||||||
SubMiner runs as an invisible Electron overlay on top of mpv. Subtitles render as an interactive layer. Move your cursor over any word and trigger a [Yomitan](https://github.com/yomidevs/yomitan) lookup. Press one key to snapshot the sentence, audio, and screenshot into Anki via AnkiConnect.
|
SubMiner runs as an invisible Electron overlay on top of mpv. Subtitles render as an interactive layer. Move your cursor over any word and trigger a [Yomitan](https://github.com/yomidevs/yomitan) lookup. Press one key to snapshot the sentence, audio, and screenshot into Anki via AnkiConnect.
|
||||||
|
|
||||||
On Windows, the recommended playback entry point is the optional `SubMiner mpv` shortcut created during setup. It launches `mpv` with SubMiner's defaults directly, so you do not need an `mpv.conf` profile just to use the shortcut.
|
On Windows, the recommended playback entry point is the optional `SubMiner mpv` shortcut created during setup. First-run setup requires the mpv plugin before it can finish. The shortcut launches `mpv` with SubMiner's defaults directly, so you do not need an `mpv.conf` profile just to use it.
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
|
|||||||
5
changes/270-first-run-setup-requires-mpv-plugin.md
Normal file
5
changes/270-first-run-setup-requires-mpv-plugin.md
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
type: changed
|
||||||
|
area: setup
|
||||||
|
|
||||||
|
- Made mpv plugin installation mandatory in the first-run setup flow, removed the skip path, and kept Finish disabled until the plugin is installed.
|
||||||
|
- Updated the Windows setup copy to make the `SubMiner mpv` shortcut the recommended playback entry point after setup completes.
|
||||||
@@ -171,7 +171,7 @@ Install `mpv` separately and ensure `mpv.exe` is on `PATH`. `ffmpeg` is still re
|
|||||||
|
|
||||||
### Windows Usage Notes
|
### Windows Usage Notes
|
||||||
|
|
||||||
- Launch `SubMiner.exe` once to let the first-run setup flow seed `%APPDATA%\\SubMiner\\config.jsonc`, offer mpv plugin installation, open bundled Yomitan settings, and optionally create `SubMiner mpv` Start Menu/Desktop shortcuts. On Windows, that shortcut is the recommended way to launch mpv playback with SubMiner defaults.
|
- Launch `SubMiner.exe` once to let the first-run setup flow seed `%APPDATA%\\SubMiner\\config.jsonc`, require mpv plugin installation, open bundled Yomitan settings, and optionally create `SubMiner mpv` Start Menu/Desktop shortcuts. On Windows, that shortcut is the recommended way to launch mpv playback with SubMiner defaults.
|
||||||
- `SubMiner.exe --launch-mpv` and the optional `SubMiner mpv` shortcut pass SubMiner's default mpv socket/subtitle args directly, including the Windows-safe subtitle search paths that skip the extra current-directory scan; they do not require an `mpv.conf` profile named `subminer`.
|
- `SubMiner.exe --launch-mpv` and the optional `SubMiner mpv` shortcut pass SubMiner's default mpv socket/subtitle args directly, including the Windows-safe subtitle search paths that skip the extra current-directory scan; they do not require an `mpv.conf` profile named `subminer`.
|
||||||
- First-run mpv plugin installs pin `binary_path` to the current `SubMiner.exe` automatically. Manual plugin configs can leave `binary_path` empty unless SubMiner is installed in a non-standard location.
|
- First-run mpv plugin installs pin `binary_path` to the current `SubMiner.exe` automatically. Manual plugin configs can leave `binary_path` empty unless SubMiner is installed in a non-standard location.
|
||||||
- Windows plugin installs rewrite `socket_path` to `\\.\pipe\subminer-socket`; do not keep `/tmp/subminer-socket` on Windows.
|
- Windows plugin installs rewrite `socket_path` to `\\.\pipe\subminer-socket`; do not keep `/tmp/subminer-socket` on Windows.
|
||||||
|
|||||||
@@ -118,6 +118,7 @@ SubMiner.AppImage --help # Show all options
|
|||||||
### Windows mpv Shortcut
|
### Windows mpv Shortcut
|
||||||
|
|
||||||
If you enabled the optional Windows shortcut during install, SubMiner creates a `SubMiner mpv` shortcut in the Start menu and/or on the desktop. On Windows, that shortcut is the recommended way to launch local files with SubMiner because it starts `mpv.exe` with the right defaults directly.
|
If you enabled the optional Windows shortcut during install, SubMiner creates a `SubMiner mpv` shortcut in the Start menu and/or on the desktop. On Windows, that shortcut is the recommended way to launch local files with SubMiner because it starts `mpv.exe` with the right defaults directly.
|
||||||
|
First-run setup requires the mpv plugin before it can finish, so the shortcut is the normal Windows playback entry point after setup completes.
|
||||||
|
|
||||||
You can use it three ways:
|
You can use it three ways:
|
||||||
|
|
||||||
@@ -157,12 +158,12 @@ SubMiner.AppImage --setup
|
|||||||
Setup flow:
|
Setup flow:
|
||||||
|
|
||||||
- config file: create the default config directory and prefer `config.jsonc`
|
- config file: create the default config directory and prefer `config.jsonc`
|
||||||
- plugin status: install or skip the bundled mpv plugin
|
- plugin status: install the bundled mpv plugin before finishing setup
|
||||||
- Yomitan shortcut: open bundled Yomitan settings directly from the setup window
|
- Yomitan shortcut: open bundled Yomitan settings directly from the setup window
|
||||||
- dictionary check: ensure at least one bundled Yomitan dictionary is available
|
- dictionary check: ensure at least one bundled Yomitan dictionary is available
|
||||||
- Windows: optionally create or remove `SubMiner mpv` Start Menu/Desktop shortcuts (`SubMiner.exe --launch-mpv`)
|
- Windows: optionally create or remove `SubMiner mpv` Start Menu/Desktop shortcuts (`SubMiner.exe --launch-mpv`)
|
||||||
- refresh: re-check plugin + dictionary state without restarting
|
- refresh: re-check plugin + dictionary state without restarting
|
||||||
- `Finish setup` stays disabled until dictionary availability is detected
|
- `Finish setup` stays disabled until the mpv plugin is installed and dictionary availability is detected
|
||||||
- finish action writes setup completion state and suppresses future auto-open prompts
|
- finish action writes setup completion state and suppresses future auto-open prompts
|
||||||
|
|
||||||
AniList character dictionary auto-sync (optional):
|
AniList character dictionary auto-sync (optional):
|
||||||
|
|||||||
15
src/main.ts
15
src/main.ts
@@ -2238,18 +2238,21 @@ const openFirstRunSetupWindowHandler = createOpenFirstRunSetupWindowHandler({
|
|||||||
firstRunSetupMessage = snapshot.message;
|
firstRunSetupMessage = snapshot.message;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (submission.action === 'skip-plugin') {
|
|
||||||
await firstRunSetupService.skipPluginInstall();
|
|
||||||
firstRunSetupMessage = 'mpv plugin installation skipped.';
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const snapshot = await firstRunSetupService.markSetupCompleted();
|
const snapshot = await firstRunSetupService.markSetupCompleted();
|
||||||
if (snapshot.state.status === 'completed') {
|
if (snapshot.state.status === 'completed') {
|
||||||
firstRunSetupMessage = null;
|
firstRunSetupMessage = null;
|
||||||
return { closeWindow: true };
|
return { closeWindow: true };
|
||||||
}
|
}
|
||||||
firstRunSetupMessage = 'Install at least one Yomitan dictionary before finishing setup.';
|
if (snapshot.pluginStatus !== 'installed') {
|
||||||
|
firstRunSetupMessage = 'Install the mpv plugin before finishing setup.';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!snapshot.externalYomitanConfigured && snapshot.dictionaryCount < 1) {
|
||||||
|
firstRunSetupMessage = 'Install at least one Yomitan dictionary before finishing setup.';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
firstRunSetupMessage = 'Finish setup requires the mpv plugin and Yomitan dictionaries.';
|
||||||
return;
|
return;
|
||||||
},
|
},
|
||||||
markSetupInProgress: async () => {
|
markSetupInProgress: async () => {
|
||||||
|
|||||||
@@ -88,7 +88,7 @@ test('setup service auto-completes legacy installs with config and dictionaries'
|
|||||||
const service = createFirstRunSetupService({
|
const service = createFirstRunSetupService({
|
||||||
configDir,
|
configDir,
|
||||||
getYomitanDictionaryCount: async () => 2,
|
getYomitanDictionaryCount: async () => 2,
|
||||||
detectPluginInstalled: () => false,
|
detectPluginInstalled: () => true,
|
||||||
installPlugin: async () => ({
|
installPlugin: async () => ({
|
||||||
ok: true,
|
ok: true,
|
||||||
pluginInstallStatus: 'installed',
|
pluginInstallStatus: 'installed',
|
||||||
@@ -106,17 +106,18 @@ test('setup service auto-completes legacy installs with config and dictionaries'
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
test('setup service requires explicit finish for incomplete installs and supports plugin skip/install', async () => {
|
test('setup service requires mpv plugin install before finish', async () => {
|
||||||
await withTempDir(async (root) => {
|
await withTempDir(async (root) => {
|
||||||
const configDir = path.join(root, 'SubMiner');
|
const configDir = path.join(root, 'SubMiner');
|
||||||
fs.mkdirSync(configDir, { recursive: true });
|
fs.mkdirSync(configDir, { recursive: true });
|
||||||
fs.writeFileSync(path.join(configDir, 'config.jsonc'), '{}');
|
fs.writeFileSync(path.join(configDir, 'config.jsonc'), '{}');
|
||||||
let dictionaryCount = 0;
|
let dictionaryCount = 0;
|
||||||
|
let pluginInstalled = false;
|
||||||
|
|
||||||
const service = createFirstRunSetupService({
|
const service = createFirstRunSetupService({
|
||||||
configDir,
|
configDir,
|
||||||
getYomitanDictionaryCount: async () => dictionaryCount,
|
getYomitanDictionaryCount: async () => dictionaryCount,
|
||||||
detectPluginInstalled: () => false,
|
detectPluginInstalled: () => pluginInstalled,
|
||||||
installPlugin: async () => ({
|
installPlugin: async () => ({
|
||||||
ok: true,
|
ok: true,
|
||||||
pluginInstallStatus: 'installed',
|
pluginInstallStatus: 'installed',
|
||||||
@@ -130,13 +131,11 @@ test('setup service requires explicit finish for incomplete installs and support
|
|||||||
assert.equal(initial.state.status, 'incomplete');
|
assert.equal(initial.state.status, 'incomplete');
|
||||||
assert.equal(initial.canFinish, false);
|
assert.equal(initial.canFinish, false);
|
||||||
|
|
||||||
const skipped = await service.skipPluginInstall();
|
|
||||||
assert.equal(skipped.state.pluginInstallStatus, 'skipped');
|
|
||||||
|
|
||||||
const installed = await service.installMpvPlugin();
|
const installed = await service.installMpvPlugin();
|
||||||
assert.equal(installed.state.pluginInstallStatus, 'installed');
|
assert.equal(installed.state.pluginInstallStatus, 'installed');
|
||||||
assert.equal(installed.pluginInstallPathSummary, '/tmp/mpv');
|
assert.equal(installed.pluginInstallPathSummary, '/tmp/mpv');
|
||||||
|
|
||||||
|
pluginInstalled = true;
|
||||||
dictionaryCount = 1;
|
dictionaryCount = 1;
|
||||||
const refreshed = await service.refreshStatus();
|
const refreshed = await service.refreshStatus();
|
||||||
assert.equal(refreshed.canFinish, true);
|
assert.equal(refreshed.canFinish, true);
|
||||||
@@ -158,7 +157,7 @@ test('setup service allows completion without internal dictionaries when externa
|
|||||||
configDir,
|
configDir,
|
||||||
getYomitanDictionaryCount: async () => 0,
|
getYomitanDictionaryCount: async () => 0,
|
||||||
isExternalYomitanConfigured: () => true,
|
isExternalYomitanConfigured: () => true,
|
||||||
detectPluginInstalled: () => false,
|
detectPluginInstalled: () => true,
|
||||||
installPlugin: async () => ({
|
installPlugin: async () => ({
|
||||||
ok: true,
|
ok: true,
|
||||||
pluginInstallStatus: 'installed',
|
pluginInstallStatus: 'installed',
|
||||||
@@ -190,7 +189,7 @@ test('setup service does not probe internal dictionaries when external yomitan i
|
|||||||
throw new Error('should not probe internal dictionaries in external mode');
|
throw new Error('should not probe internal dictionaries in external mode');
|
||||||
},
|
},
|
||||||
isExternalYomitanConfigured: () => true,
|
isExternalYomitanConfigured: () => true,
|
||||||
detectPluginInstalled: () => false,
|
detectPluginInstalled: () => true,
|
||||||
installPlugin: async () => ({
|
installPlugin: async () => ({
|
||||||
ok: true,
|
ok: true,
|
||||||
pluginInstallStatus: 'installed',
|
pluginInstallStatus: 'installed',
|
||||||
@@ -218,7 +217,7 @@ test('setup service reopens when external-yomitan completion later has no extern
|
|||||||
configDir,
|
configDir,
|
||||||
getYomitanDictionaryCount: async () => 0,
|
getYomitanDictionaryCount: async () => 0,
|
||||||
isExternalYomitanConfigured: () => true,
|
isExternalYomitanConfigured: () => true,
|
||||||
detectPluginInstalled: () => false,
|
detectPluginInstalled: () => true,
|
||||||
installPlugin: async () => ({
|
installPlugin: async () => ({
|
||||||
ok: true,
|
ok: true,
|
||||||
pluginInstallStatus: 'installed',
|
pluginInstallStatus: 'installed',
|
||||||
@@ -235,7 +234,7 @@ test('setup service reopens when external-yomitan completion later has no extern
|
|||||||
configDir,
|
configDir,
|
||||||
getYomitanDictionaryCount: async () => 0,
|
getYomitanDictionaryCount: async () => 0,
|
||||||
isExternalYomitanConfigured: () => false,
|
isExternalYomitanConfigured: () => false,
|
||||||
detectPluginInstalled: () => false,
|
detectPluginInstalled: () => true,
|
||||||
installPlugin: async () => ({
|
installPlugin: async () => ({
|
||||||
ok: true,
|
ok: true,
|
||||||
pluginInstallStatus: 'installed',
|
pluginInstallStatus: 'installed',
|
||||||
@@ -262,7 +261,7 @@ test('setup service keeps completed when external-yomitan completion later has i
|
|||||||
configDir,
|
configDir,
|
||||||
getYomitanDictionaryCount: async () => 0,
|
getYomitanDictionaryCount: async () => 0,
|
||||||
isExternalYomitanConfigured: () => true,
|
isExternalYomitanConfigured: () => true,
|
||||||
detectPluginInstalled: () => false,
|
detectPluginInstalled: () => true,
|
||||||
installPlugin: async () => ({
|
installPlugin: async () => ({
|
||||||
ok: true,
|
ok: true,
|
||||||
pluginInstallStatus: 'installed',
|
pluginInstallStatus: 'installed',
|
||||||
@@ -279,7 +278,7 @@ test('setup service keeps completed when external-yomitan completion later has i
|
|||||||
configDir,
|
configDir,
|
||||||
getYomitanDictionaryCount: async () => 2,
|
getYomitanDictionaryCount: async () => 2,
|
||||||
isExternalYomitanConfigured: () => false,
|
isExternalYomitanConfigured: () => false,
|
||||||
detectPluginInstalled: () => false,
|
detectPluginInstalled: () => true,
|
||||||
installPlugin: async () => ({
|
installPlugin: async () => ({
|
||||||
ok: true,
|
ok: true,
|
||||||
pluginInstallStatus: 'installed',
|
pluginInstallStatus: 'installed',
|
||||||
@@ -304,7 +303,7 @@ test('setup service marks cancelled when popup closes before completion', async
|
|||||||
const service = createFirstRunSetupService({
|
const service = createFirstRunSetupService({
|
||||||
configDir,
|
configDir,
|
||||||
getYomitanDictionaryCount: async () => 0,
|
getYomitanDictionaryCount: async () => 0,
|
||||||
detectPluginInstalled: () => false,
|
detectPluginInstalled: () => true,
|
||||||
installPlugin: async () => ({
|
installPlugin: async () => ({
|
||||||
ok: true,
|
ok: true,
|
||||||
pluginInstallStatus: 'installed',
|
pluginInstallStatus: 'installed',
|
||||||
@@ -331,7 +330,7 @@ test('setup service reflects detected Windows mpv shortcuts before preferences a
|
|||||||
platform: 'win32',
|
platform: 'win32',
|
||||||
configDir,
|
configDir,
|
||||||
getYomitanDictionaryCount: async () => 0,
|
getYomitanDictionaryCount: async () => 0,
|
||||||
detectPluginInstalled: () => false,
|
detectPluginInstalled: () => true,
|
||||||
installPlugin: async () => ({
|
installPlugin: async () => ({
|
||||||
ok: true,
|
ok: true,
|
||||||
pluginInstallStatus: 'installed',
|
pluginInstallStatus: 'installed',
|
||||||
@@ -364,7 +363,7 @@ test('setup service persists Windows mpv shortcut preferences and status with on
|
|||||||
platform: 'win32',
|
platform: 'win32',
|
||||||
configDir,
|
configDir,
|
||||||
getYomitanDictionaryCount: async () => 0,
|
getYomitanDictionaryCount: async () => 0,
|
||||||
detectPluginInstalled: () => false,
|
detectPluginInstalled: () => true,
|
||||||
installPlugin: async () => ({
|
installPlugin: async () => ({
|
||||||
ok: true,
|
ok: true,
|
||||||
pluginInstallStatus: 'installed',
|
pluginInstallStatus: 'installed',
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ export interface SetupStatusSnapshot {
|
|||||||
dictionaryCount: number;
|
dictionaryCount: number;
|
||||||
canFinish: boolean;
|
canFinish: boolean;
|
||||||
externalYomitanConfigured: boolean;
|
externalYomitanConfigured: boolean;
|
||||||
pluginStatus: 'installed' | 'optional' | 'skipped' | 'failed';
|
pluginStatus: 'installed' | 'required' | 'failed';
|
||||||
pluginInstallPathSummary: string | null;
|
pluginInstallPathSummary: string | null;
|
||||||
windowsMpvShortcuts: SetupWindowsMpvShortcutSnapshot;
|
windowsMpvShortcuts: SetupWindowsMpvShortcutSnapshot;
|
||||||
message: string | null;
|
message: string | null;
|
||||||
@@ -48,7 +48,6 @@ export interface FirstRunSetupService {
|
|||||||
markSetupInProgress: () => Promise<SetupStatusSnapshot>;
|
markSetupInProgress: () => Promise<SetupStatusSnapshot>;
|
||||||
markSetupCancelled: () => Promise<SetupStatusSnapshot>;
|
markSetupCancelled: () => Promise<SetupStatusSnapshot>;
|
||||||
markSetupCompleted: () => Promise<SetupStatusSnapshot>;
|
markSetupCompleted: () => Promise<SetupStatusSnapshot>;
|
||||||
skipPluginInstall: () => Promise<SetupStatusSnapshot>;
|
|
||||||
installMpvPlugin: () => Promise<SetupStatusSnapshot>;
|
installMpvPlugin: () => Promise<SetupStatusSnapshot>;
|
||||||
configureWindowsMpvShortcuts: (preferences: {
|
configureWindowsMpvShortcuts: (preferences: {
|
||||||
startMenuEnabled: boolean;
|
startMenuEnabled: boolean;
|
||||||
@@ -108,9 +107,8 @@ function getPluginStatus(
|
|||||||
pluginInstalled: boolean,
|
pluginInstalled: boolean,
|
||||||
): SetupStatusSnapshot['pluginStatus'] {
|
): SetupStatusSnapshot['pluginStatus'] {
|
||||||
if (pluginInstalled) return 'installed';
|
if (pluginInstalled) return 'installed';
|
||||||
if (state.pluginInstallStatus === 'skipped') return 'skipped';
|
|
||||||
if (state.pluginInstallStatus === 'failed') return 'failed';
|
if (state.pluginInstallStatus === 'failed') return 'failed';
|
||||||
return 'optional';
|
return 'required';
|
||||||
}
|
}
|
||||||
|
|
||||||
function getWindowsMpvShortcutStatus(
|
function getWindowsMpvShortcutStatus(
|
||||||
@@ -230,11 +228,13 @@ export function createFirstRunSetupService(deps: {
|
|||||||
return {
|
return {
|
||||||
configReady,
|
configReady,
|
||||||
dictionaryCount,
|
dictionaryCount,
|
||||||
canFinish: isYomitanSetupSatisfied({
|
canFinish:
|
||||||
configReady,
|
pluginInstalled &&
|
||||||
dictionaryCount,
|
isYomitanSetupSatisfied({
|
||||||
externalYomitanConfigured,
|
configReady,
|
||||||
}),
|
dictionaryCount,
|
||||||
|
externalYomitanConfigured,
|
||||||
|
}),
|
||||||
externalYomitanConfigured,
|
externalYomitanConfigured,
|
||||||
pluginStatus: getPluginStatus(state, pluginInstalled),
|
pluginStatus: getPluginStatus(state, pluginInstalled),
|
||||||
pluginInstallPathSummary: state.pluginInstallPathSummary,
|
pluginInstallPathSummary: state.pluginInstallPathSummary,
|
||||||
@@ -347,8 +347,6 @@ export function createFirstRunSetupService(deps: {
|
|||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
skipPluginInstall: async () =>
|
|
||||||
refreshWithState(writeState({ ...readState(), pluginInstallStatus: 'skipped' })),
|
|
||||||
installMpvPlugin: async () => {
|
installMpvPlugin: async () => {
|
||||||
const result = await deps.installPlugin();
|
const result = await deps.installPlugin();
|
||||||
return refreshWithState(
|
return refreshWithState(
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ test('buildFirstRunSetupHtml renders macchiato setup actions and disabled finish
|
|||||||
dictionaryCount: 0,
|
dictionaryCount: 0,
|
||||||
canFinish: false,
|
canFinish: false,
|
||||||
externalYomitanConfigured: false,
|
externalYomitanConfigured: false,
|
||||||
pluginStatus: 'optional',
|
pluginStatus: 'required',
|
||||||
pluginInstallPathSummary: null,
|
pluginInstallPathSummary: null,
|
||||||
windowsMpvShortcuts: {
|
windowsMpvShortcuts: {
|
||||||
supported: false,
|
supported: false,
|
||||||
@@ -29,6 +29,7 @@ test('buildFirstRunSetupHtml renders macchiato setup actions and disabled finish
|
|||||||
|
|
||||||
assert.match(html, /SubMiner setup/);
|
assert.match(html, /SubMiner setup/);
|
||||||
assert.match(html, /Install mpv plugin/);
|
assert.match(html, /Install mpv plugin/);
|
||||||
|
assert.match(html, /Required before SubMiner setup can finish/);
|
||||||
assert.match(html, /Open Yomitan Settings/);
|
assert.match(html, /Open Yomitan Settings/);
|
||||||
assert.match(html, /Finish setup/);
|
assert.match(html, /Finish setup/);
|
||||||
assert.match(html, /disabled/);
|
assert.match(html, /disabled/);
|
||||||
@@ -62,7 +63,7 @@ test('buildFirstRunSetupHtml explains external yomitan mode and keeps finish ena
|
|||||||
dictionaryCount: 0,
|
dictionaryCount: 0,
|
||||||
canFinish: true,
|
canFinish: true,
|
||||||
externalYomitanConfigured: true,
|
externalYomitanConfigured: true,
|
||||||
pluginStatus: 'optional',
|
pluginStatus: 'installed',
|
||||||
pluginInstallPathSummary: null,
|
pluginInstallPathSummary: null,
|
||||||
windowsMpvShortcuts: {
|
windowsMpvShortcuts: {
|
||||||
supported: false,
|
supported: false,
|
||||||
@@ -76,16 +77,14 @@ test('buildFirstRunSetupHtml explains external yomitan mode and keeps finish ena
|
|||||||
});
|
});
|
||||||
|
|
||||||
assert.match(html, /External profile configured/);
|
assert.match(html, /External profile configured/);
|
||||||
assert.match(
|
assert.match(html, /Finish stays unlocked while SubMiner is reusing an external Yomitan profile\./);
|
||||||
html,
|
|
||||||
/Finish stays unlocked while SubMiner is reusing an external Yomitan profile\./,
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test('parseFirstRunSetupSubmissionUrl parses supported custom actions', () => {
|
test('parseFirstRunSetupSubmissionUrl parses supported custom actions', () => {
|
||||||
assert.deepEqual(parseFirstRunSetupSubmissionUrl('subminer://first-run-setup?action=refresh'), {
|
assert.deepEqual(parseFirstRunSetupSubmissionUrl('subminer://first-run-setup?action=refresh'), {
|
||||||
action: 'refresh',
|
action: 'refresh',
|
||||||
});
|
});
|
||||||
|
assert.equal(parseFirstRunSetupSubmissionUrl('subminer://first-run-setup?action=skip-plugin'), null);
|
||||||
assert.equal(parseFirstRunSetupSubmissionUrl('https://example.com'), null);
|
assert.equal(parseFirstRunSetupSubmissionUrl('https://example.com'), null);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -146,7 +145,7 @@ test('closing incomplete first-run setup quits app outside background mode', asy
|
|||||||
dictionaryCount: 0,
|
dictionaryCount: 0,
|
||||||
canFinish: false,
|
canFinish: false,
|
||||||
externalYomitanConfigured: false,
|
externalYomitanConfigured: false,
|
||||||
pluginStatus: 'optional',
|
pluginStatus: 'required',
|
||||||
pluginInstallPathSummary: null,
|
pluginInstallPathSummary: null,
|
||||||
windowsMpvShortcuts: {
|
windowsMpvShortcuts: {
|
||||||
supported: false,
|
supported: false,
|
||||||
|
|||||||
@@ -19,7 +19,6 @@ export type FirstRunSetupAction =
|
|||||||
| 'configure-windows-mpv-shortcuts'
|
| 'configure-windows-mpv-shortcuts'
|
||||||
| 'open-yomitan-settings'
|
| 'open-yomitan-settings'
|
||||||
| 'refresh'
|
| 'refresh'
|
||||||
| 'skip-plugin'
|
|
||||||
| 'finish';
|
| 'finish';
|
||||||
|
|
||||||
export interface FirstRunSetupSubmission {
|
export interface FirstRunSetupSubmission {
|
||||||
@@ -33,7 +32,7 @@ export interface FirstRunSetupHtmlModel {
|
|||||||
dictionaryCount: number;
|
dictionaryCount: number;
|
||||||
canFinish: boolean;
|
canFinish: boolean;
|
||||||
externalYomitanConfigured: boolean;
|
externalYomitanConfigured: boolean;
|
||||||
pluginStatus: 'installed' | 'optional' | 'skipped' | 'failed';
|
pluginStatus: 'installed' | 'required' | 'failed';
|
||||||
pluginInstallPathSummary: string | null;
|
pluginInstallPathSummary: string | null;
|
||||||
windowsMpvShortcuts: {
|
windowsMpvShortcuts: {
|
||||||
supported: boolean;
|
supported: boolean;
|
||||||
@@ -64,19 +63,15 @@ export function buildFirstRunSetupHtml(model: FirstRunSetupHtmlModel): string {
|
|||||||
const pluginLabel =
|
const pluginLabel =
|
||||||
model.pluginStatus === 'installed'
|
model.pluginStatus === 'installed'
|
||||||
? 'Installed'
|
? 'Installed'
|
||||||
: model.pluginStatus === 'skipped'
|
: model.pluginStatus === 'failed'
|
||||||
? 'Skipped'
|
? 'Failed'
|
||||||
: model.pluginStatus === 'failed'
|
: 'Required';
|
||||||
? 'Failed'
|
|
||||||
: 'Optional';
|
|
||||||
const pluginTone =
|
const pluginTone =
|
||||||
model.pluginStatus === 'installed'
|
model.pluginStatus === 'installed'
|
||||||
? 'ready'
|
? 'ready'
|
||||||
: model.pluginStatus === 'failed'
|
: model.pluginStatus === 'failed'
|
||||||
? 'danger'
|
? 'danger'
|
||||||
: model.pluginStatus === 'skipped'
|
: 'warn';
|
||||||
? 'muted'
|
|
||||||
: 'warn';
|
|
||||||
const windowsShortcutLabel =
|
const windowsShortcutLabel =
|
||||||
model.windowsMpvShortcuts.status === 'installed'
|
model.windowsMpvShortcuts.status === 'installed'
|
||||||
? 'Installed'
|
? 'Installed'
|
||||||
@@ -129,8 +124,10 @@ export function buildFirstRunSetupHtml(model: FirstRunSetupHtmlModel): string {
|
|||||||
? 'ready'
|
? 'ready'
|
||||||
: 'warn';
|
: 'warn';
|
||||||
const footerMessage = model.externalYomitanConfigured
|
const footerMessage = model.externalYomitanConfigured
|
||||||
? 'Finish stays unlocked while SubMiner is reusing an external Yomitan profile. If you later launch without yomitan.externalProfilePath, setup will require at least one internal dictionary.'
|
? model.pluginStatus === 'installed'
|
||||||
: 'Finish stays locked until Yomitan reports at least one installed dictionary.';
|
? 'Finish stays unlocked while SubMiner is reusing an external Yomitan profile. If you later launch without yomitan.externalProfilePath, setup will require at least one internal dictionary.'
|
||||||
|
: 'Finish stays locked until the mpv plugin is installed. If you later launch without yomitan.externalProfilePath, setup will also require at least one internal dictionary.'
|
||||||
|
: 'Finish stays locked until the mpv plugin is installed and Yomitan reports at least one installed dictionary.';
|
||||||
|
|
||||||
return `<!doctype html>
|
return `<!doctype html>
|
||||||
<html>
|
<html>
|
||||||
@@ -269,6 +266,7 @@ export function buildFirstRunSetupHtml(model: FirstRunSetupHtmlModel): string {
|
|||||||
<div>
|
<div>
|
||||||
<strong>mpv plugin</strong>
|
<strong>mpv plugin</strong>
|
||||||
<div class="meta">${escapeHtml(model.pluginInstallPathSummary ?? 'Default mpv scripts location')}</div>
|
<div class="meta">${escapeHtml(model.pluginInstallPathSummary ?? 'Default mpv scripts location')}</div>
|
||||||
|
<div class="meta">Required before SubMiner setup can finish.</div>
|
||||||
</div>
|
</div>
|
||||||
${renderStatusBadge(pluginLabel, pluginTone)}
|
${renderStatusBadge(pluginLabel, pluginTone)}
|
||||||
</div>
|
</div>
|
||||||
@@ -284,7 +282,6 @@ export function buildFirstRunSetupHtml(model: FirstRunSetupHtmlModel): string {
|
|||||||
<button onclick="window.location.href='subminer://first-run-setup?action=install-plugin'">${pluginActionLabel}</button>
|
<button onclick="window.location.href='subminer://first-run-setup?action=install-plugin'">${pluginActionLabel}</button>
|
||||||
<button onclick="window.location.href='subminer://first-run-setup?action=open-yomitan-settings'">Open Yomitan Settings</button>
|
<button onclick="window.location.href='subminer://first-run-setup?action=open-yomitan-settings'">Open Yomitan Settings</button>
|
||||||
<button class="ghost" onclick="window.location.href='subminer://first-run-setup?action=refresh'">Refresh status</button>
|
<button class="ghost" onclick="window.location.href='subminer://first-run-setup?action=refresh'">Refresh status</button>
|
||||||
<button class="ghost" onclick="window.location.href='subminer://first-run-setup?action=skip-plugin'">Skip plugin</button>
|
|
||||||
<button class="primary" ${model.canFinish ? '' : 'disabled'} onclick="window.location.href='subminer://first-run-setup?action=finish'">Finish setup</button>
|
<button class="primary" ${model.canFinish ? '' : 'disabled'} onclick="window.location.href='subminer://first-run-setup?action=finish'">Finish setup</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="message">${model.message ? escapeHtml(model.message) : ''}</div>
|
<div class="message">${model.message ? escapeHtml(model.message) : ''}</div>
|
||||||
@@ -305,7 +302,6 @@ export function parseFirstRunSetupSubmissionUrl(rawUrl: string): FirstRunSetupSu
|
|||||||
action !== 'configure-windows-mpv-shortcuts' &&
|
action !== 'configure-windows-mpv-shortcuts' &&
|
||||||
action !== 'open-yomitan-settings' &&
|
action !== 'open-yomitan-settings' &&
|
||||||
action !== 'refresh' &&
|
action !== 'refresh' &&
|
||||||
action !== 'skip-plugin' &&
|
|
||||||
action !== 'finish'
|
action !== 'finish'
|
||||||
) {
|
) {
|
||||||
return null;
|
return null;
|
||||||
|
|||||||
Reference in New Issue
Block a user