mirror of
https://github.com/ksyasuda/SubMiner.git
synced 2026-06-10 15:13:32 -07:00
fix: Kiku field grouping, frequency particles, sidebar media, Yomitan popup visibility (#91)
This commit is contained in:
@@ -96,6 +96,7 @@ export interface MainIpcRuntimeServiceDepsParams {
|
||||
getAnilistQueueStatus: IpcDepsRuntimeOptions['getAnilistQueueStatus'];
|
||||
retryAnilistQueueNow: IpcDepsRuntimeOptions['retryAnilistQueueNow'];
|
||||
runAnilistPostWatchUpdateOnManualMark?: IpcDepsRuntimeOptions['runAnilistPostWatchUpdateOnManualMark'];
|
||||
recordSubtitleMiningContext?: IpcDepsRuntimeOptions['recordSubtitleMiningContext'];
|
||||
getCharacterDictionarySelection?: IpcDepsRuntimeOptions['getCharacterDictionarySelection'];
|
||||
setCharacterDictionarySelection?: IpcDepsRuntimeOptions['setCharacterDictionarySelection'];
|
||||
getCharacterDictionaryManagerSnapshot?: IpcDepsRuntimeOptions['getCharacterDictionaryManagerSnapshot'];
|
||||
@@ -273,6 +274,7 @@ export function createMainIpcRuntimeServiceDeps(
|
||||
getAnilistQueueStatus: params.getAnilistQueueStatus,
|
||||
retryAnilistQueueNow: params.retryAnilistQueueNow,
|
||||
runAnilistPostWatchUpdateOnManualMark: params.runAnilistPostWatchUpdateOnManualMark,
|
||||
recordSubtitleMiningContext: params.recordSubtitleMiningContext,
|
||||
getCharacterDictionarySelection: params.getCharacterDictionarySelection,
|
||||
setCharacterDictionarySelection: params.setCharacterDictionarySelection,
|
||||
getCharacterDictionaryManagerSnapshot: params.getCharacterDictionaryManagerSnapshot,
|
||||
|
||||
@@ -804,6 +804,28 @@ test('waitForModalOpen resolves true after modal acknowledgement', async () => {
|
||||
assert.equal(await pending, true);
|
||||
});
|
||||
|
||||
test('waitForModalOpen resolves true when modal acknowledgement arrives before waiter registration', async () => {
|
||||
const modalWindow = createMockWindow();
|
||||
const runtime = createOverlayModalRuntimeService({
|
||||
getMainWindow: () => null,
|
||||
getModalWindow: () => modalWindow as never,
|
||||
createModalWindow: () => modalWindow as never,
|
||||
getModalGeometry: () => ({ x: 0, y: 0, width: 400, height: 300 }),
|
||||
setModalWindowBounds: () => {},
|
||||
});
|
||||
|
||||
runtime.sendToActiveOverlayWindow(
|
||||
'kiku:field-grouping-request',
|
||||
{},
|
||||
{
|
||||
restoreOnModalClose: 'kiku',
|
||||
},
|
||||
);
|
||||
runtime.notifyOverlayModalOpened('kiku');
|
||||
|
||||
assert.equal(await runtime.waitForModalOpen('kiku', 5), true);
|
||||
});
|
||||
|
||||
test('waitForModalOpen resolves false on timeout', async () => {
|
||||
const runtime = createOverlayModalRuntimeService({
|
||||
getMainWindow: () => null,
|
||||
|
||||
@@ -64,6 +64,7 @@ export function createOverlayModalRuntimeService(
|
||||
): OverlayModalRuntime {
|
||||
const restoreVisibleOverlayOnModalClose = new Set<OverlayHostedModal>();
|
||||
const modalOpenWaiters = new Map<OverlayHostedModal, Array<(opened: boolean) => void>>();
|
||||
const openedModals = new Set<OverlayHostedModal>();
|
||||
let modalActive = false;
|
||||
let mainWindowMousePassthroughForcedByModal = false;
|
||||
let mainWindowHiddenByModal = false;
|
||||
@@ -375,6 +376,7 @@ export function createOverlayModalRuntimeService(
|
||||
};
|
||||
|
||||
const handleOverlayModalClosed = (modal: OverlayHostedModal): void => {
|
||||
openedModals.delete(modal);
|
||||
if (!restoreVisibleOverlayOnModalClose.has(modal)) return;
|
||||
restoreVisibleOverlayOnModalClose.delete(modal);
|
||||
const modalWindow = deps.getModalWindow();
|
||||
@@ -392,6 +394,7 @@ export function createOverlayModalRuntimeService(
|
||||
|
||||
const notifyOverlayModalOpened = (modal: OverlayHostedModal): void => {
|
||||
if (!restoreVisibleOverlayOnModalClose.has(modal)) return;
|
||||
openedModals.add(modal);
|
||||
const waiters = modalOpenWaiters.get(modal) ?? [];
|
||||
modalOpenWaiters.delete(modal);
|
||||
for (const resolve of waiters) {
|
||||
@@ -420,6 +423,10 @@ export function createOverlayModalRuntimeService(
|
||||
|
||||
const waitForModalOpen = async (modal: OverlayHostedModal, timeoutMs: number): Promise<boolean> =>
|
||||
await new Promise<boolean>((resolve) => {
|
||||
if (openedModals.has(modal)) {
|
||||
resolve(true);
|
||||
return;
|
||||
}
|
||||
const waiters = modalOpenWaiters.get(modal) ?? [];
|
||||
const finish = (opened: boolean): void => {
|
||||
clearTimeout(timeout);
|
||||
|
||||
@@ -7,7 +7,7 @@ type FieldGroupingOverlayMainDeps<TModal extends string> = Omit<
|
||||
sendToActiveOverlayWindow: (
|
||||
channel: string,
|
||||
payload?: unknown,
|
||||
runtimeOptions?: { restoreOnModalClose?: TModal },
|
||||
runtimeOptions?: { restoreOnModalClose?: TModal; preferModalWindow?: boolean },
|
||||
) => boolean;
|
||||
};
|
||||
|
||||
@@ -31,7 +31,7 @@ export function createBuildFieldGroupingOverlayMainDepsHandler<TModal extends st
|
||||
sendToVisibleOverlay: (
|
||||
channel: string,
|
||||
payload?: unknown,
|
||||
runtimeOptions?: { restoreOnModalClose?: TModal },
|
||||
runtimeOptions?: { restoreOnModalClose?: TModal; preferModalWindow?: boolean },
|
||||
) => deps.sendToActiveOverlayWindow(channel, payload, runtimeOptions),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ type LogCandidate = {
|
||||
mtimeMs: number;
|
||||
mtimeDateKey: string;
|
||||
fileDateKey: string | null;
|
||||
fileWeekKey: string | null;
|
||||
};
|
||||
|
||||
export type ExportLogsResult = {
|
||||
@@ -38,10 +39,21 @@ function localDateKey(date: Date): string {
|
||||
return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())}`;
|
||||
}
|
||||
|
||||
function localWeekKey(date: Date): string {
|
||||
const startOfYear = new Date(date.getFullYear(), 0, 1);
|
||||
const dayOfYear =
|
||||
Math.floor((date.getTime() - startOfYear.getTime()) / (24 * 60 * 60 * 1000)) + 1;
|
||||
return `${date.getFullYear()}-W${pad(Math.max(1, Math.ceil(dayOfYear / 7)))}`;
|
||||
}
|
||||
|
||||
function filenameDateKey(fileName: string): string | null {
|
||||
return fileName.match(/\d{4}-\d{2}-\d{2}/)?.[0] ?? null;
|
||||
}
|
||||
|
||||
function filenameWeekKey(fileName: string): string | null {
|
||||
return fileName.match(/\d{4}-W\d{2}/)?.[0] ?? null;
|
||||
}
|
||||
|
||||
function fileKind(fileName: string): string {
|
||||
const match = fileName.match(/^([A-Za-z0-9_-]+)-/);
|
||||
return match?.[1] ?? fileName;
|
||||
@@ -84,6 +96,7 @@ function buildCandidate(logsDir: string, entry: string): LogCandidate | null {
|
||||
mtimeMs: stats.mtimeMs,
|
||||
mtimeDateKey: localDateKey(stats.mtime),
|
||||
fileDateKey: filenameDateKey(entry),
|
||||
fileWeekKey: filenameWeekKey(entry),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -117,6 +130,14 @@ function candidateFreshnessMs(candidate: LogCandidate): number {
|
||||
if (candidate.fileDateKey) {
|
||||
return Date.parse(`${candidate.fileDateKey}T23:59:59.999Z`);
|
||||
}
|
||||
if (candidate.fileWeekKey) {
|
||||
const match = candidate.fileWeekKey.match(/^(\d{4})-W(\d{2})$/);
|
||||
if (match) {
|
||||
const year = Number(match[1]);
|
||||
const week = Number(match[2]);
|
||||
return Date.UTC(year, 0, week * 7, 23, 59, 59, 999);
|
||||
}
|
||||
}
|
||||
return candidate.mtimeMs;
|
||||
}
|
||||
|
||||
@@ -130,6 +151,12 @@ function selectLogCandidates(
|
||||
return { mode: 'current-day', selected: currentDated };
|
||||
}
|
||||
|
||||
const currentWeek = localWeekKey(now);
|
||||
const currentWeekly = candidates.filter((candidate) => candidate.fileWeekKey === currentWeek);
|
||||
if (currentWeekly.length > 0) {
|
||||
return { mode: 'current-day', selected: currentWeekly };
|
||||
}
|
||||
|
||||
const currentUndated = candidates.filter(
|
||||
(candidate) => candidate.fileDateKey === null && candidate.mtimeDateKey === today,
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user