mirror of
https://github.com/ksyasuda/SubMiner.git
synced 2026-03-01 18:22:41 -08:00
docs: document Kiku/Lapis integration in config and anki pages
This commit is contained in:
@@ -14,7 +14,7 @@
|
|||||||
|
|
||||||
<div align="center">
|
<div align="center">
|
||||||
|
|
||||||
[](./assets/minecard.mp4)
|
[](./assets/minecard.mp4)
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -69,7 +69,7 @@ mkdir -p ~/.config/SubMiner && cp /tmp/config.example.jsonc ~/.config/SubMiner/c
|
|||||||
### 3. Set up Yomitan Dictionaries
|
### 3. Set up Yomitan Dictionaries
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
subminer app --start --yomitan
|
subminer app --yomitan
|
||||||
```
|
```
|
||||||
|
|
||||||
### 4. Mine
|
### 4. Mine
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
# Anki Integration
|
# Anki Integration
|
||||||
|
|
||||||
SubMiner uses the [AnkiConnect](https://ankiweb.net/shared/info/2055492159) add-on to create and update Anki cards with sentence context, audio, and screenshots.
|
SubMiner uses the [AnkiConnect](https://ankiweb.net/shared/info/2055492159) add-on to create and update Anki cards with sentence context, audio, and screenshots.
|
||||||
|
This project is built primarily for [Kiku](https://kiku.youyoumu.my.id/) and [Lapis](https://github.com/donkuri/lapis) note types, including sentence-card and field-grouping behavior.
|
||||||
|
|
||||||
## Prerequisites
|
## Prerequisites
|
||||||
|
|
||||||
|
|||||||
@@ -97,6 +97,7 @@ The configuration file includes several main sections:
|
|||||||
**Anki Integration**
|
**Anki Integration**
|
||||||
|
|
||||||
- [**AnkiConnect**](#ankiconnect) - Automatic Anki card creation with media
|
- [**AnkiConnect**](#ankiconnect) - Automatic Anki card creation with media
|
||||||
|
- [**Kiku/Lapis Integration**](#kiku-lapis-integration) - Sentence cards and duplicate handling for Kiku/Lapis note types
|
||||||
- [**N+1 Word Highlighting**](#n1-word-highlighting) - Known-word cache and single-target highlighting
|
- [**N+1 Word Highlighting**](#n1-word-highlighting) - Known-word cache and single-target highlighting
|
||||||
- [**Field Grouping Modes**](#field-grouping-modes) - Kiku/Lapis duplicate card merging
|
- [**Field Grouping Modes**](#field-grouping-modes) - Kiku/Lapis duplicate card merging
|
||||||
|
|
||||||
@@ -662,13 +663,28 @@ This example is intentionally compact. The option table below documents availabl
|
|||||||
| `isLapis` | object | Lapis/shared sentence-card config: `{ enabled, sentenceCardModel }`. Sentence/audio field names are fixed to `Sentence` and `SentenceAudio`. |
|
| `isLapis` | object | Lapis/shared sentence-card config: `{ enabled, sentenceCardModel }`. Sentence/audio field names are fixed to `Sentence` and `SentenceAudio`. |
|
||||||
| `isKiku` | object | Kiku-only config: `{ enabled, fieldGrouping, deleteDuplicateInAuto }` (shared sentence/audio/model settings are inherited from `isLapis`) |
|
| `isKiku` | object | Kiku-only config: `{ enabled, fieldGrouping, deleteDuplicateInAuto }` (shared sentence/audio/model settings are inherited from `isLapis`) |
|
||||||
|
|
||||||
**Kiku / Lapis Note Type Support:**
|
### Kiku/Lapis Integration
|
||||||
|
|
||||||
SubMiner supports the [Lapis](https://github.com/donkuri/lapis) and [Kiku](https://kiku.youyoumu.my.id/) note types. Both `isLapis.enabled` and `isKiku.enabled` can be true; Kiku takes precedence for grouping behavior, while sentence-card model/field settings come from `isLapis`.
|
SubMiner is intentionally built for [Kiku](https://kiku.youyoumu.my.id/) and [Lapis](https://github.com/donkuri/lapis) workflows, with note-type-specific behavior built into Anki settings.
|
||||||
|
|
||||||
When enabled, sentence cards automatically set `IsSentenceCard` to `"x"` and populate the `Expression` field. Audio cards set `IsAudioCard` to `"x"`.
|
```jsonc
|
||||||
|
"ankiConnect": {
|
||||||
|
"isLapis": {
|
||||||
|
"enabled": true,
|
||||||
|
"sentenceCardModel": "Japanese sentences"
|
||||||
|
},
|
||||||
|
"isKiku": {
|
||||||
|
"enabled": true,
|
||||||
|
"fieldGrouping": "manual",
|
||||||
|
"deleteDuplicateInAuto": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
Kiku extends Lapis with **field grouping** — when a duplicate card is detected (same Word/Expression), SubMiner merges the two cards' content into one using Kiku's `data-group-id` HTML structure, organizing each mining instance into separate pages within the note.
|
- Enable `isLapis` to mine dedicated sentence cards. SubMiner sets `IsSentenceCard` to `"x"` and fills the sentence fields for the configured model.
|
||||||
|
- Enable `isKiku` to turn on duplicate merge behavior for mined Word/Expression hits.
|
||||||
|
- When both are enabled, Kiku behavior is applied for grouping while sentence-card model settings are still read from `isLapis`.
|
||||||
|
- `isKiku.fieldGrouping` supports `disabled`, `auto`, and `manual` merge modes; see [Field Grouping Modes](#field-grouping-modes).
|
||||||
|
|
||||||
### N+1 Word Highlighting
|
### N+1 Word Highlighting
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,15 @@
|
|||||||
# Usage
|
# Usage
|
||||||
|
|
||||||
|
> [!IMPORTANT]
|
||||||
|
> SubMiner requires the bundled Yomitan instance to have at least one dictionary imported for lookups to work.
|
||||||
|
> See [Yomitan setup](#yomitan-setup) for details.
|
||||||
|
|
||||||
There are two ways to use SubMiner — the `subminer` wrapper script or the mpv plugin:
|
There are two ways to use SubMiner — the `subminer` wrapper script or the mpv plugin:
|
||||||
|
|
||||||
| Approach | Best For |
|
| Approach | Best For |
|
||||||
| ------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
| ------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
||||||
| **subminer script** | All-in-one solution. Handles video selection, launches MPV with the correct socket, and manages app commands. With default plugin settings, overlay auto-starts visible and playback resumes after annotation readiness. |
|
| **subminer script** | All-in-one solution. Handles video selection, launches MPV with the correct socket, and manages app commands. With default plugin settings, overlay auto-starts visible and playback resumes after annotation readiness. |
|
||||||
| **MPV plugin** | When you launch MPV yourself or from other tools. Provides in-MPV chord keybindings (e.g. `y-y` for menu) to control overlay visibility. Requires `--input-ipc-server=/tmp/subminer-socket`. |
|
| **MPV plugin** | When you launch MPV yourself or from other tools. Provides in-MPV chord keybindings (e.g. `y-y` for menu) to control overlay visibility. Requires `--input-ipc-server=/tmp/subminer-socket`. |
|
||||||
|
|
||||||
You can use both together—install the plugin for on-demand control, but use `subminer` when you want the streamlined workflow.
|
You can use both together—install the plugin for on-demand control, but use `subminer` when you want the streamlined workflow.
|
||||||
|
|
||||||
@@ -147,6 +151,14 @@ secondary-sub-visibility=no
|
|||||||
|
|
||||||
`secondary-slang` is not an mpv option; use `slang` with `sid=auto` / `secondary-sid=auto` instead.
|
`secondary-slang` is not an mpv option; use `slang` with `sid=auto` / `secondary-sid=auto` instead.
|
||||||
|
|
||||||
|
### Yomitan setup
|
||||||
|
|
||||||
|
SubMiner includes a bundled Yomitan extension for overlay word lookup. This bundled extension is separate from any Yomitan browser extension you may have installed.
|
||||||
|
|
||||||
|
For SubMiner overlay lookups to work, open Yomitan settings (`subminer app --settings` or `SubMiner.AppImage --settings`) and import at least one dictionary in the bundled Yomitan instance.
|
||||||
|
|
||||||
|
If you also use Yomitan in a browser, configure that browser profile separately; it does not inherit dictionaries or settings from the bundled instance.
|
||||||
|
|
||||||
### YouTube Playback
|
### YouTube Playback
|
||||||
|
|
||||||
`subminer` accepts direct URLs (for example, YouTube links) and `ytsearch:` targets, and forwards them to mpv.
|
`subminer` accepts direct URLs (for example, YouTube links) and `ytsearch:` targets, and forwards them to mpv.
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import test from 'node:test';
|
import test from 'node:test';
|
||||||
import assert from 'node:assert/strict';
|
import assert from 'node:assert/strict';
|
||||||
import { hasExplicitCommand, parseArgs, shouldStartApp } from './args';
|
import { hasExplicitCommand, parseArgs, shouldRunSettingsOnlyStartup, shouldStartApp } from './args';
|
||||||
|
|
||||||
test('parseArgs parses booleans and value flags', () => {
|
test('parseArgs parses booleans and value flags', () => {
|
||||||
const args = parseArgs([
|
const args = parseArgs([
|
||||||
@@ -60,6 +60,28 @@ test('hasExplicitCommand and shouldStartApp preserve command intent', () => {
|
|||||||
assert.equal(hasExplicitCommand(refreshKnownWords), true);
|
assert.equal(hasExplicitCommand(refreshKnownWords), true);
|
||||||
assert.equal(shouldStartApp(refreshKnownWords), false);
|
assert.equal(shouldStartApp(refreshKnownWords), false);
|
||||||
|
|
||||||
|
const settings = parseArgs(['--settings']);
|
||||||
|
assert.equal(settings.settings, true);
|
||||||
|
assert.equal(hasExplicitCommand(settings), true);
|
||||||
|
assert.equal(shouldStartApp(settings), true);
|
||||||
|
assert.equal(shouldRunSettingsOnlyStartup(settings), true);
|
||||||
|
|
||||||
|
const settingsWithOverlay = parseArgs(['--settings', '--toggle-visible-overlay']);
|
||||||
|
assert.equal(settingsWithOverlay.settings, true);
|
||||||
|
assert.equal(settingsWithOverlay.toggleVisibleOverlay, true);
|
||||||
|
assert.equal(shouldRunSettingsOnlyStartup(settingsWithOverlay), false);
|
||||||
|
|
||||||
|
const yomitanAlias = parseArgs(['--yomitan']);
|
||||||
|
assert.equal(yomitanAlias.settings, true);
|
||||||
|
assert.equal(hasExplicitCommand(yomitanAlias), true);
|
||||||
|
assert.equal(shouldStartApp(yomitanAlias), true);
|
||||||
|
|
||||||
|
const help = parseArgs(['--help']);
|
||||||
|
assert.equal(help.help, true);
|
||||||
|
assert.equal(hasExplicitCommand(help), true);
|
||||||
|
assert.equal(shouldStartApp(help), false);
|
||||||
|
assert.equal(shouldRunSettingsOnlyStartup(help), false);
|
||||||
|
|
||||||
const anilistStatus = parseArgs(['--anilist-status']);
|
const anilistStatus = parseArgs(['--anilist-status']);
|
||||||
assert.equal(anilistStatus.anilistStatus, true);
|
assert.equal(anilistStatus.anilistStatus, true);
|
||||||
assert.equal(hasExplicitCommand(anilistStatus), true);
|
assert.equal(hasExplicitCommand(anilistStatus), true);
|
||||||
|
|||||||
@@ -295,6 +295,7 @@ export function shouldStartApp(args: CliArgs): boolean {
|
|||||||
args.start ||
|
args.start ||
|
||||||
args.toggle ||
|
args.toggle ||
|
||||||
args.toggleVisibleOverlay ||
|
args.toggleVisibleOverlay ||
|
||||||
|
args.settings ||
|
||||||
args.copySubtitle ||
|
args.copySubtitle ||
|
||||||
args.copySubtitleMultiple ||
|
args.copySubtitleMultiple ||
|
||||||
args.mineSentence ||
|
args.mineSentence ||
|
||||||
@@ -314,6 +315,50 @@ export function shouldStartApp(args: CliArgs): boolean {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function shouldRunSettingsOnlyStartup(args: CliArgs): boolean {
|
||||||
|
return (
|
||||||
|
args.settings &&
|
||||||
|
!args.background &&
|
||||||
|
!args.start &&
|
||||||
|
!args.stop &&
|
||||||
|
!args.toggle &&
|
||||||
|
!args.toggleVisibleOverlay &&
|
||||||
|
!args.show &&
|
||||||
|
!args.hide &&
|
||||||
|
!args.showVisibleOverlay &&
|
||||||
|
!args.hideVisibleOverlay &&
|
||||||
|
!args.copySubtitle &&
|
||||||
|
!args.copySubtitleMultiple &&
|
||||||
|
!args.mineSentence &&
|
||||||
|
!args.mineSentenceMultiple &&
|
||||||
|
!args.updateLastCardFromClipboard &&
|
||||||
|
!args.refreshKnownWords &&
|
||||||
|
!args.toggleSecondarySub &&
|
||||||
|
!args.triggerFieldGrouping &&
|
||||||
|
!args.triggerSubsync &&
|
||||||
|
!args.markAudioCard &&
|
||||||
|
!args.openRuntimeOptions &&
|
||||||
|
!args.anilistStatus &&
|
||||||
|
!args.anilistLogout &&
|
||||||
|
!args.anilistSetup &&
|
||||||
|
!args.anilistRetryQueue &&
|
||||||
|
!args.jellyfin &&
|
||||||
|
!args.jellyfinLogin &&
|
||||||
|
!args.jellyfinLogout &&
|
||||||
|
!args.jellyfinLibraries &&
|
||||||
|
!args.jellyfinItems &&
|
||||||
|
!args.jellyfinSubtitles &&
|
||||||
|
!args.jellyfinPlay &&
|
||||||
|
!args.jellyfinRemoteAnnounce &&
|
||||||
|
!args.texthooker &&
|
||||||
|
!args.help &&
|
||||||
|
!args.autoStartOverlay &&
|
||||||
|
!args.generateConfig &&
|
||||||
|
!args.backupOverwrite &&
|
||||||
|
!args.debug
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
export function commandNeedsOverlayRuntime(args: CliArgs): boolean {
|
export function commandNeedsOverlayRuntime(args: CliArgs): boolean {
|
||||||
return (
|
return (
|
||||||
args.toggle ||
|
args.toggle ||
|
||||||
|
|||||||
@@ -66,6 +66,55 @@ test('runAppReadyRuntime starts websocket in auto mode when plugin missing', asy
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('runAppReadyRuntime skips heavy startup when shouldSkipHeavyStartup returns true', async () => {
|
||||||
|
const { deps, calls } = makeDeps({
|
||||||
|
shouldSkipHeavyStartup: () => true,
|
||||||
|
reloadConfig: () => calls.push('reloadConfig'),
|
||||||
|
getResolvedConfig: () => {
|
||||||
|
calls.push('getResolvedConfig');
|
||||||
|
return {
|
||||||
|
websocket: { enabled: 'auto' },
|
||||||
|
secondarySub: {},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
getConfigWarnings: () => {
|
||||||
|
calls.push('getConfigWarnings');
|
||||||
|
return [];
|
||||||
|
},
|
||||||
|
setLogLevel: (level, source) => calls.push(`setLogLevel:${level}:${source}`),
|
||||||
|
initRuntimeOptionsManager: () => calls.push('initRuntimeOptionsManager'),
|
||||||
|
startBackgroundWarmups: () => calls.push('startBackgroundWarmups'),
|
||||||
|
loadSubtitlePosition: () => calls.push('loadSubtitlePosition'),
|
||||||
|
resolveKeybindings: () => calls.push('resolveKeybindings'),
|
||||||
|
createMpvClient: () => calls.push('createMpvClient'),
|
||||||
|
logConfigWarning: () => calls.push('logConfigWarning'),
|
||||||
|
startJellyfinRemoteSession: async () => {
|
||||||
|
calls.push('startJellyfinRemoteSession');
|
||||||
|
},
|
||||||
|
createImmersionTracker: () => calls.push('createImmersionTracker'),
|
||||||
|
handleInitialArgs: () => calls.push('handleInitialArgs'),
|
||||||
|
});
|
||||||
|
|
||||||
|
await runAppReadyRuntime(deps);
|
||||||
|
|
||||||
|
assert.equal(calls.includes('reloadConfig'), false);
|
||||||
|
assert.equal(calls.includes('getResolvedConfig'), false);
|
||||||
|
assert.equal(calls.includes('getConfigWarnings'), false);
|
||||||
|
assert.equal(calls.includes('setLogLevel:warn:config'), false);
|
||||||
|
assert.equal(calls.includes('startBackgroundWarmups'), false);
|
||||||
|
assert.equal(calls.includes('loadSubtitlePosition'), false);
|
||||||
|
assert.equal(calls.includes('resolveKeybindings'), false);
|
||||||
|
assert.equal(calls.includes('createMpvClient'), false);
|
||||||
|
assert.equal(calls.includes('initRuntimeOptionsManager'), false);
|
||||||
|
assert.equal(calls.includes('createImmersionTracker'), false);
|
||||||
|
assert.equal(calls.includes('startJellyfinRemoteSession'), false);
|
||||||
|
assert.equal(calls.includes('logConfigWarning'), false);
|
||||||
|
assert.equal(calls.includes('handleInitialArgs'), true);
|
||||||
|
assert.equal(calls.includes('loadYomitanExtension'), true);
|
||||||
|
assert.equal(calls[0], 'loadYomitanExtension');
|
||||||
|
assert.equal(calls[calls.length - 1], 'handleInitialArgs');
|
||||||
|
});
|
||||||
|
|
||||||
test('runAppReadyRuntime skips Jellyfin remote startup when dependency is not wired', async () => {
|
test('runAppReadyRuntime skips Jellyfin remote startup when dependency is not wired', async () => {
|
||||||
const { deps, calls } = makeDeps({
|
const { deps, calls } = makeDeps({
|
||||||
startJellyfinRemoteSession: undefined,
|
startJellyfinRemoteSession: undefined,
|
||||||
|
|||||||
@@ -121,6 +121,7 @@ export interface AppReadyRuntimeDeps {
|
|||||||
logDebug?: (message: string) => void;
|
logDebug?: (message: string) => void;
|
||||||
onCriticalConfigErrors?: (errors: string[]) => void;
|
onCriticalConfigErrors?: (errors: string[]) => void;
|
||||||
now?: () => number;
|
now?: () => number;
|
||||||
|
shouldSkipHeavyStartup?: () => boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const REQUIRED_ANKI_FIELD_MAPPING_KEYS = [
|
const REQUIRED_ANKI_FIELD_MAPPING_KEYS = [
|
||||||
@@ -169,6 +170,13 @@ export async function runAppReadyRuntime(deps: AppReadyRuntimeDeps): Promise<voi
|
|||||||
const startupStartedAtMs = now();
|
const startupStartedAtMs = now();
|
||||||
deps.logDebug?.('App-ready critical path started.');
|
deps.logDebug?.('App-ready critical path started.');
|
||||||
|
|
||||||
|
if (deps.shouldSkipHeavyStartup?.()) {
|
||||||
|
await deps.loadYomitanExtension();
|
||||||
|
deps.handleInitialArgs();
|
||||||
|
deps.logDebug?.(`App-ready critical path finished in ${now() - startupStartedAtMs}ms.`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
deps.reloadConfig();
|
deps.reloadConfig();
|
||||||
const config = deps.getResolvedConfig();
|
const config = deps.getResolvedConfig();
|
||||||
const criticalConfigErrors = getStartupCriticalConfigErrors(config);
|
const criticalConfigErrors = getStartupCriticalConfigErrors(config);
|
||||||
|
|||||||
@@ -101,7 +101,12 @@ import { SubtitleTimingTracker } from './subtitle-timing-tracker';
|
|||||||
import { RuntimeOptionsManager } from './runtime-options';
|
import { RuntimeOptionsManager } from './runtime-options';
|
||||||
import { downloadToFile, isRemoteMediaPath, parseMediaInfo } from './jimaku/utils';
|
import { downloadToFile, isRemoteMediaPath, parseMediaInfo } from './jimaku/utils';
|
||||||
import { createLogger, setLogLevel, type LogLevelSource } from './logger';
|
import { createLogger, setLogLevel, type LogLevelSource } from './logger';
|
||||||
import { commandNeedsOverlayRuntime, parseArgs, shouldStartApp } from './cli/args';
|
import {
|
||||||
|
commandNeedsOverlayRuntime,
|
||||||
|
parseArgs,
|
||||||
|
shouldRunSettingsOnlyStartup,
|
||||||
|
shouldStartApp,
|
||||||
|
} from './cli/args';
|
||||||
import type { CliArgs, CliCommandSource } from './cli/args';
|
import type { CliArgs, CliCommandSource } from './cli/args';
|
||||||
import { printHelp } from './cli/help';
|
import { printHelp } from './cli/help';
|
||||||
import {
|
import {
|
||||||
@@ -2163,6 +2168,8 @@ const { reloadConfig: reloadConfigHandler, appReadyRuntimeRunner } = composeAppR
|
|||||||
: configDerivedRuntime.shouldAutoInitializeOverlayRuntimeFromConfig(),
|
: configDerivedRuntime.shouldAutoInitializeOverlayRuntimeFromConfig(),
|
||||||
initializeOverlayRuntime: () => initializeOverlayRuntime(),
|
initializeOverlayRuntime: () => initializeOverlayRuntime(),
|
||||||
handleInitialArgs: () => handleInitialArgs(),
|
handleInitialArgs: () => handleInitialArgs(),
|
||||||
|
shouldSkipHeavyStartup: () =>
|
||||||
|
Boolean(appState.initialArgs && shouldRunSettingsOnlyStartup(appState.initialArgs)),
|
||||||
createImmersionTracker: () => {
|
createImmersionTracker: () => {
|
||||||
ensureImmersionTrackerStarted();
|
ensureImmersionTrackerStarted();
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -48,6 +48,7 @@ export interface AppReadyRuntimeDepsFactoryInput {
|
|||||||
onCriticalConfigErrors?: AppReadyRuntimeDeps['onCriticalConfigErrors'];
|
onCriticalConfigErrors?: AppReadyRuntimeDeps['onCriticalConfigErrors'];
|
||||||
logDebug?: AppReadyRuntimeDeps['logDebug'];
|
logDebug?: AppReadyRuntimeDeps['logDebug'];
|
||||||
now?: AppReadyRuntimeDeps['now'];
|
now?: AppReadyRuntimeDeps['now'];
|
||||||
|
shouldSkipHeavyStartup?: AppReadyRuntimeDeps['shouldSkipHeavyStartup'];
|
||||||
}
|
}
|
||||||
|
|
||||||
export function createAppLifecycleRuntimeDeps(
|
export function createAppLifecycleRuntimeDeps(
|
||||||
@@ -103,6 +104,7 @@ export function createAppReadyRuntimeDeps(
|
|||||||
onCriticalConfigErrors: params.onCriticalConfigErrors,
|
onCriticalConfigErrors: params.onCriticalConfigErrors,
|
||||||
logDebug: params.logDebug,
|
logDebug: params.logDebug,
|
||||||
now: params.now,
|
now: params.now,
|
||||||
|
shouldSkipHeavyStartup: params.shouldSkipHeavyStartup,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -31,5 +31,6 @@ export function createBuildAppReadyRuntimeMainDepsHandler(deps: AppReadyRuntimeD
|
|||||||
onCriticalConfigErrors: deps.onCriticalConfigErrors,
|
onCriticalConfigErrors: deps.onCriticalConfigErrors,
|
||||||
logDebug: deps.logDebug,
|
logDebug: deps.logDebug,
|
||||||
now: deps.now,
|
now: deps.now,
|
||||||
|
shouldSkipHeavyStartup: deps.shouldSkipHeavyStartup,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user