mirror of
https://github.com/oliverschwendener/ueli.git
synced 2024-10-04 18:17:43 +03:00
Improved error logging when file icon extraction fails
This commit is contained in:
parent
98f8f81900
commit
db4cde8be2
BIN
assets/Extensions/ApplicationSearch/macos-generic-app-icon.png
Normal file
BIN
assets/Extensions/ApplicationSearch/macos-generic-app-icon.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 5.7 KiB |
BIN
assets/Extensions/ApplicationSearch/windows-applications.png
Normal file
BIN
assets/Extensions/ApplicationSearch/windows-applications.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.1 KiB |
BIN
assets/Extensions/ApplicationSearch/windows-generic-app-icon.png
Normal file
BIN
assets/Extensions/ApplicationSearch/windows-generic-app-icon.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.6 KiB |
@ -11,6 +11,7 @@ export type ContextBridge = {
|
||||
on: IpcRenderer["on"];
|
||||
};
|
||||
|
||||
resetChache: () => Promise<void>;
|
||||
copyTextToClipboard: (textToCopy: string) => void;
|
||||
extensionDisabled: (extensionId: string) => void;
|
||||
extensionEnabled: (extensionId: string) => void;
|
||||
|
@ -25,6 +25,11 @@ export class ExtensionManagerModule {
|
||||
|
||||
await extensionManager.populateSearchIndex();
|
||||
|
||||
ipcMain.handle("resetCache", async () => {
|
||||
await dependencyRegistry.get("FileImageGenerator").clearCache();
|
||||
await extensionManager.populateSearchIndex();
|
||||
});
|
||||
|
||||
ipcMain.on("getExtensionTranslations", (event) => {
|
||||
event.returnValue = extensionManager.getSupportedExtensions().map((extension) => ({
|
||||
extensionId: extension.id,
|
||||
|
@ -1,5 +1,6 @@
|
||||
export interface FileSystemUtility {
|
||||
createFolderIfDoesntExist(folderPath: string): Promise<void>;
|
||||
clearFolder(folderPath: string): Promise<void>;
|
||||
pathExists(fileOrFolderPath: string): Promise<boolean>;
|
||||
readJsonFile<T>(filePath: string): Promise<T>;
|
||||
readJsonFileSync<T>(filePath: string): T;
|
||||
|
@ -5,14 +5,21 @@ import {
|
||||
mkdir,
|
||||
readFile,
|
||||
readFileSync,
|
||||
readdir,
|
||||
statSync,
|
||||
unlink,
|
||||
writeFile,
|
||||
writeFileSync,
|
||||
} from "fs";
|
||||
import { join } from "path";
|
||||
import type { FileSystemUtility } from "./Contract";
|
||||
|
||||
export class NodeJsFileSystemUtility implements FileSystemUtility {
|
||||
public async clearFolder(folderPath: string): Promise<void> {
|
||||
const filePaths = await this.readDirectory(folderPath);
|
||||
await Promise.all(filePaths.map((filePath) => this.removeFile(filePath)));
|
||||
}
|
||||
|
||||
public async createFolderIfDoesntExist(folderPath: string): Promise<void> {
|
||||
const exists = await this.pathExists(folderPath);
|
||||
|
||||
@ -110,4 +117,16 @@ export class NodeJsFileSystemUtility implements FileSystemUtility {
|
||||
private readFileSync(filePath: string): Buffer {
|
||||
return readFileSync(filePath);
|
||||
}
|
||||
|
||||
private readDirectory(folderPath: string): Promise<string[]> {
|
||||
return new Promise((resolve, reject) => {
|
||||
readdir(folderPath, (error, fileNames) => {
|
||||
if (error) {
|
||||
reject(error);
|
||||
} else {
|
||||
resolve(fileNames.map((fileName) => join(folderPath, fileName)));
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -2,4 +2,5 @@ import type { Image } from "@common/Core/Image";
|
||||
|
||||
export interface FileImageGenerator {
|
||||
getImage(filePath: string): Promise<Image>;
|
||||
clearCache: () => Promise<void>;
|
||||
}
|
||||
|
@ -8,6 +8,32 @@ import { FileImageGenerator } from "./FileImageGenerator";
|
||||
describe(FileImageGenerator, () => {
|
||||
const cacheFolderPath = "cacheFolderPath";
|
||||
|
||||
it("should clear cache folder if it exists", async () => {
|
||||
const pathExistsMock = vi.fn().mockReturnValue(true);
|
||||
const clearFolderMock = vi.fn().mockReturnValue(Promise.resolve());
|
||||
|
||||
const fileSystemUtility = <FileSystemUtility>{
|
||||
pathExists: (f) => pathExistsMock(f),
|
||||
clearFolder: (f) => clearFolderMock(f),
|
||||
};
|
||||
|
||||
const fileImageGenerator = new FileImageGenerator(cacheFolderPath, fileSystemUtility);
|
||||
|
||||
await fileImageGenerator.clearCache();
|
||||
expect(pathExistsMock).toHaveBeenCalledWith(cacheFolderPath);
|
||||
expect(clearFolderMock).toHaveBeenCalledWith(cacheFolderPath);
|
||||
});
|
||||
|
||||
it("should do nothing if cache folder does not exist", async () => {
|
||||
const pathExistsMock = vi.fn().mockReturnValue(false);
|
||||
const fileSystemUtility = <FileSystemUtility>{ pathExists: (f) => pathExistsMock(f) };
|
||||
|
||||
const fileImageGenerator = new FileImageGenerator(cacheFolderPath, fileSystemUtility);
|
||||
|
||||
await fileImageGenerator.clearCache();
|
||||
expect(pathExistsMock).toHaveBeenCalledWith(cacheFolderPath);
|
||||
});
|
||||
|
||||
it("should create the cached file if it doesn't exist and return the image", async () => {
|
||||
const pathExistsMock = vi.fn().mockReturnValue(false);
|
||||
const writePngMock = vi.fn().mockReturnValue(Promise.resolve());
|
||||
|
@ -11,6 +11,14 @@ export class FileImageGenerator implements FileImageGeneratorInterface {
|
||||
private readonly fileSystemUtility: FileSystemUtility,
|
||||
) {}
|
||||
|
||||
public async clearCache(): Promise<void> {
|
||||
const exists = await this.fileSystemUtility.pathExists(this.cacheFolderPath);
|
||||
|
||||
if (exists) {
|
||||
await this.fileSystemUtility.clearFolder(this.cacheFolderPath);
|
||||
}
|
||||
}
|
||||
|
||||
public async getImage(filePath: string): Promise<Image> {
|
||||
const cachedPngFilePath = await this.ensureCachedPngFileExists(filePath);
|
||||
|
||||
@ -23,7 +31,13 @@ export class FileImageGenerator implements FileImageGeneratorInterface {
|
||||
const exists = await this.fileSystemUtility.pathExists(cachedPngFilePath);
|
||||
|
||||
if (!exists) {
|
||||
await this.fileSystemUtility.writePng(getFileIcon(filePath), cachedPngFilePath);
|
||||
const buffer = getFileIcon(filePath);
|
||||
|
||||
if (!buffer.byteLength) {
|
||||
throw new Error(`getFileIcon returned Buffer with length 0`);
|
||||
}
|
||||
|
||||
await this.fileSystemUtility.writePng(buffer, cachedPngFilePath);
|
||||
}
|
||||
|
||||
return cachedPngFilePath;
|
||||
|
@ -1,3 +1,4 @@
|
||||
import type { BrowserWindowNotifier } from "@Core/BrowserWindowNotifier";
|
||||
import type { Clock } from "@Core/Clock/Clock";
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import { Logger } from "./Logger";
|
||||
@ -7,45 +8,65 @@ describe(Logger, () => {
|
||||
|
||||
it("should log an error", () => {
|
||||
const getCurrentTimeAsStringMock = vi.fn().mockReturnValue(nowAsString);
|
||||
const notifyMock = vi.fn();
|
||||
|
||||
const logger = new Logger(<Clock>{ getCurrentTimeAsString: () => getCurrentTimeAsStringMock() });
|
||||
const logger = new Logger(
|
||||
<Clock>{ getCurrentTimeAsString: () => getCurrentTimeAsStringMock() },
|
||||
<BrowserWindowNotifier>{ notify: (c) => notifyMock(c) },
|
||||
);
|
||||
|
||||
logger.error("This is an error");
|
||||
|
||||
expect(logger.getLogs()).toEqual([`[${nowAsString}][ERROR] This is an error`]);
|
||||
expect(getCurrentTimeAsStringMock).toHaveBeenCalledOnce();
|
||||
expect(notifyMock).toHaveBeenCalledWith("logsUpdated");
|
||||
});
|
||||
|
||||
it("should log a debug message", () => {
|
||||
const getCurrentTimeAsStringMock = vi.fn().mockReturnValue(nowAsString);
|
||||
const notifyMock = vi.fn();
|
||||
|
||||
const logger = new Logger(<Clock>{ getCurrentTimeAsString: () => getCurrentTimeAsStringMock() });
|
||||
const logger = new Logger(
|
||||
<Clock>{ getCurrentTimeAsString: () => getCurrentTimeAsStringMock() },
|
||||
<BrowserWindowNotifier>{ notify: (c) => notifyMock(c) },
|
||||
);
|
||||
|
||||
logger.debug("This is a debug message");
|
||||
|
||||
expect(logger.getLogs()).toEqual([`[${nowAsString}][DEBUG] This is a debug message`]);
|
||||
expect(getCurrentTimeAsStringMock).toHaveBeenCalledOnce();
|
||||
expect(notifyMock).toHaveBeenCalledWith("logsUpdated");
|
||||
});
|
||||
|
||||
it("should log a info message", () => {
|
||||
const getCurrentTimeAsStringMock = vi.fn().mockReturnValue(nowAsString);
|
||||
const notifyMock = vi.fn();
|
||||
|
||||
const logger = new Logger(<Clock>{ getCurrentTimeAsString: () => getCurrentTimeAsStringMock() });
|
||||
const logger = new Logger(
|
||||
<Clock>{ getCurrentTimeAsString: () => getCurrentTimeAsStringMock() },
|
||||
<BrowserWindowNotifier>{ notify: (c) => notifyMock(c) },
|
||||
);
|
||||
|
||||
logger.info("This is a info message");
|
||||
|
||||
expect(logger.getLogs()).toEqual([`[${nowAsString}][INFO] This is a info message`]);
|
||||
expect(getCurrentTimeAsStringMock).toHaveBeenCalledOnce();
|
||||
expect(notifyMock).toHaveBeenCalledWith("logsUpdated");
|
||||
});
|
||||
|
||||
it("should log a warning", () => {
|
||||
const getCurrentTimeAsStringMock = vi.fn().mockReturnValue(nowAsString);
|
||||
const notifyMock = vi.fn();
|
||||
|
||||
const logger = new Logger(<Clock>{ getCurrentTimeAsString: () => getCurrentTimeAsStringMock() });
|
||||
const logger = new Logger(
|
||||
<Clock>{ getCurrentTimeAsString: () => getCurrentTimeAsStringMock() },
|
||||
<BrowserWindowNotifier>{ notify: (c) => notifyMock(c) },
|
||||
);
|
||||
|
||||
logger.warn("This is a warning");
|
||||
|
||||
expect(logger.getLogs()).toEqual([`[${nowAsString}][WARNING] This is a warning`]);
|
||||
expect(getCurrentTimeAsStringMock).toHaveBeenCalledOnce();
|
||||
expect(notifyMock).toHaveBeenCalledWith("logsUpdated");
|
||||
});
|
||||
});
|
||||
|
@ -1,10 +1,14 @@
|
||||
import type { BrowserWindowNotifier } from "@Core/BrowserWindowNotifier";
|
||||
import type { Clock } from "../Clock";
|
||||
import type { Logger as LoggerInterface } from "./Contract";
|
||||
|
||||
export class Logger implements LoggerInterface {
|
||||
private logs: string[] = [];
|
||||
|
||||
public constructor(private readonly clock: Clock) {}
|
||||
public constructor(
|
||||
private readonly clock: Clock,
|
||||
private readonly browserWindowNotifier: BrowserWindowNotifier,
|
||||
) {}
|
||||
|
||||
public error(message: string): void {
|
||||
this.writeLog("ERROR", message);
|
||||
@ -28,5 +32,6 @@ export class Logger implements LoggerInterface {
|
||||
|
||||
private writeLog(logLevel: string, message: string) {
|
||||
this.logs.push(`[${this.clock.getCurrentTimeAsString()}][${logLevel}] ${message}`);
|
||||
this.browserWindowNotifier.notify("logsUpdated");
|
||||
}
|
||||
}
|
||||
|
@ -5,9 +5,8 @@ import { Logger } from "./Logger";
|
||||
export class LoggerModule {
|
||||
public static bootstrap(dependencyRegistry: DependencyRegistry<Dependencies>) {
|
||||
const ipcMain = dependencyRegistry.get("IpcMain");
|
||||
const clock = dependencyRegistry.get("Clock");
|
||||
|
||||
const logger = new Logger(clock);
|
||||
const logger = new Logger(dependencyRegistry.get("Clock"), dependencyRegistry.get("BrowserWindowNotifier"));
|
||||
|
||||
dependencyRegistry.register("Logger", logger);
|
||||
ipcMain.on("getLogs", (event) => (event.returnValue = logger.getLogs()));
|
||||
|
@ -21,7 +21,7 @@ export class ApplicationSearch implements Extension {
|
||||
};
|
||||
|
||||
public constructor(
|
||||
private readonly currentOperatingSystem: OperatingSystem,
|
||||
private readonly operatingSystem: OperatingSystem,
|
||||
private readonly applicationRepository: ApplicationRepository,
|
||||
private readonly settings: Settings,
|
||||
private readonly assetPathResolver: AssetPathResolver,
|
||||
@ -34,7 +34,7 @@ export class ApplicationSearch implements Extension {
|
||||
|
||||
public isSupported(): boolean {
|
||||
const supportedOperatingSystems: OperatingSystem[] = ["Windows", "macOS"];
|
||||
return supportedOperatingSystems.includes(this.currentOperatingSystem);
|
||||
return supportedOperatingSystems.includes(this.operatingSystem);
|
||||
}
|
||||
|
||||
public getSettingDefaultValue<T>(key: string): T {
|
||||
@ -51,8 +51,14 @@ export class ApplicationSearch implements Extension {
|
||||
}
|
||||
|
||||
public getImage(): Image {
|
||||
const fileNames: Record<OperatingSystem, string> = {
|
||||
Linux: null, // not supported,
|
||||
macOS: "macos-applications.png",
|
||||
Windows: "windows-applications.png",
|
||||
};
|
||||
|
||||
return {
|
||||
url: `file://${this.assetPathResolver.getExtensionAssetPath(this.id, "macos-applications.png")}`,
|
||||
url: `file://${this.assetPathResolver.getExtensionAssetPath(this.id, fileNames[this.operatingSystem])}`,
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -23,12 +23,14 @@ export class ApplicationSearchModule implements ExtensionModule {
|
||||
dependencyRegistry.get("FileImageGenerator"),
|
||||
dependencyRegistry.get("Logger"),
|
||||
settings,
|
||||
dependencyRegistry.get("AssetPathResolver"),
|
||||
),
|
||||
Windows: new WindowsApplicationRepository(
|
||||
dependencyRegistry.get("PowershellUtility"),
|
||||
settings,
|
||||
dependencyRegistry.get("FileImageGenerator"),
|
||||
dependencyRegistry.get("Logger"),
|
||||
dependencyRegistry.get("AssetPathResolver"),
|
||||
),
|
||||
Linux: undefined, // not supported
|
||||
};
|
||||
|
@ -1,3 +1,4 @@
|
||||
import type { AssetPathResolver } from "@Core/AssetPathResolver";
|
||||
import type { FileImageGenerator } from "@Core/ImageGenerator";
|
||||
import type { Logger } from "@Core/Logger";
|
||||
import type { PowershellUtility } from "@Core/PowershellUtility";
|
||||
@ -15,6 +16,7 @@ export class WindowsApplicationRepository implements ApplicationRepository {
|
||||
private readonly settings: Settings,
|
||||
private readonly fileImageGenerator: FileImageGenerator,
|
||||
private readonly logger: Logger,
|
||||
private readonly assetPathResolver: AssetPathResolver,
|
||||
) {}
|
||||
|
||||
public async getApplications(): Promise<Application[]> {
|
||||
@ -40,7 +42,14 @@ export class WindowsApplicationRepository implements ApplicationRepository {
|
||||
const appIcons = await this.getAppIcons(windowsApplicationRetrieverResults.map(({ FullName }) => FullName));
|
||||
|
||||
return windowsApplicationRetrieverResults.map(
|
||||
({ BaseName, FullName }) => new Application(BaseName, FullName, appIcons[FullName]),
|
||||
({ BaseName, FullName }) =>
|
||||
new Application(
|
||||
BaseName,
|
||||
FullName,
|
||||
appIcons[FullName] ?? {
|
||||
url: `file://${this.assetPathResolver.getExtensionAssetPath("ApplicationSearch", "windows-generic-app-icon.png")}`,
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -1,3 +1,4 @@
|
||||
import type { AssetPathResolver } from "@Core/AssetPathResolver";
|
||||
import type { CommandlineUtility } from "@Core/CommandlineUtility";
|
||||
import type { FileImageGenerator } from "@Core/ImageGenerator";
|
||||
import type { Logger } from "@Core/Logger";
|
||||
@ -13,6 +14,7 @@ export class MacOsApplicationRepository implements ApplicationRepository {
|
||||
private readonly fileImageGenerator: FileImageGenerator,
|
||||
private readonly logger: Logger,
|
||||
private readonly settings: Settings,
|
||||
private readonly assetPathResolver: AssetPathResolver,
|
||||
) {}
|
||||
|
||||
public async getApplications(): Promise<Application[]> {
|
||||
@ -21,7 +23,16 @@ export class MacOsApplicationRepository implements ApplicationRepository {
|
||||
|
||||
return filePaths
|
||||
.filter((filePath) => !!icons[filePath])
|
||||
.map((filePath) => new Application(parse(filePath).name, filePath, icons[filePath]));
|
||||
.map(
|
||||
(filePath) =>
|
||||
new Application(
|
||||
parse(filePath).name,
|
||||
filePath,
|
||||
icons[filePath] ?? {
|
||||
url: `file://${this.assetPathResolver.getExtensionAssetPath("ApplicationSearch", "macos-generic-app-icon.png")}`,
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
private async getAllFilePaths(): Promise<string[]> {
|
||||
|
@ -26,6 +26,8 @@ import * as Extensions from "./Extensions";
|
||||
dependencyRegistry.register("SafeStorage", Electron.safeStorage);
|
||||
|
||||
// Core Modules
|
||||
Core.EventEmitterModule.bootstrap(dependencyRegistry);
|
||||
Core.EventSubscriberModule.bootstrap(dependencyRegistry);
|
||||
Core.RandomStringProviderModule.bootstrap(dependencyRegistry);
|
||||
Core.SafeStorageEncryptionModule.bootstrap(dependencyRegistry);
|
||||
Core.AssetPathResolverModule.bootstrap(dependencyRegistry);
|
||||
@ -33,10 +35,8 @@ import * as Extensions from "./Extensions";
|
||||
Core.ClipboardModule.bootstrap(dependencyRegistry);
|
||||
Core.ClockModule.bootstrap(dependencyRegistry);
|
||||
Core.AboutUeliModule.bootstrap(dependencyRegistry);
|
||||
Core.LoggerModule.bootstrap(dependencyRegistry);
|
||||
Core.EventEmitterModule.bootstrap(dependencyRegistry);
|
||||
Core.EventSubscriberModule.bootstrap(dependencyRegistry);
|
||||
Core.BrowserWindowNotifierModule.bootstrap(dependencyRegistry);
|
||||
Core.LoggerModule.bootstrap(dependencyRegistry);
|
||||
Core.CommandlineUtilityModule.bootstrap(dependencyRegistry);
|
||||
Core.FileSystemUtilityModule.bootstrap(dependencyRegistry);
|
||||
await Core.PowershellUtilityModule.bootstrap(dependencyRegistry);
|
||||
|
@ -6,6 +6,7 @@ const contextBridgeImplementation: ContextBridge = {
|
||||
on: (channel, listener) => ipcRenderer.on(channel, listener),
|
||||
},
|
||||
|
||||
resetChache: () => ipcRenderer.invoke("resetCache"),
|
||||
copyTextToClipboard: (textToCopy) => ipcRenderer.send("copyTextToClipboard", { textToCopy }),
|
||||
extensionDisabled: (extensionId) => ipcRenderer.send("extensionDisabled", { extensionId }),
|
||||
extensionEnabled: (extensionId) => ipcRenderer.send("extensionEnabled", { extensionId }),
|
||||
|
@ -102,7 +102,16 @@ export const getCoreTranslations = (): { namespace: string; translations: Transl
|
||||
translations: {
|
||||
"en-US": {
|
||||
title: "Debug",
|
||||
resetCache: "Reset cache",
|
||||
resetCacheHint: "This might help when troubleshooting issues with icons.",
|
||||
resettingCache: "Resetting cache...",
|
||||
resetCacheDialogTitle: "Are you sure?",
|
||||
resetCacheDialogContent: "You are about to reset the cache. This might take a few seconds.",
|
||||
resetCacheCancel: "Cancel",
|
||||
resetCacheConfirm: "Reset Cache",
|
||||
resetAllSettings: "Reset all settings",
|
||||
resetAllSettingsHint:
|
||||
"This resets all settings to their default values. You will lose all settings that you changed.",
|
||||
resetAllSettingsButton: "Reset",
|
||||
resetAllSettingsDialogTitle: "Are you sure?",
|
||||
resetAllSettingsDialogContent: "You are about to reset all settings to their default values.",
|
||||
@ -111,7 +120,17 @@ export const getCoreTranslations = (): { namespace: string; translations: Transl
|
||||
},
|
||||
"de-CH": {
|
||||
title: "Fehlerbehebung",
|
||||
resetCache: "Zwischenspeicher zurücksetzen",
|
||||
resetCacheHint: "Dies kann bei der Behebung von Problemen mit Symbolen hilfreich sein.",
|
||||
resettingCache: "Zwischenspeicher wird zurückgesetzt...",
|
||||
resetCacheDialogTitle: "Bist du sicher?",
|
||||
resetCacheDialogContent:
|
||||
"Du bist dabei, den Cache zurückzusetzen. Dies kann ein paar Sekunden dauern.",
|
||||
resetCacheCancel: "Abbrechen",
|
||||
resetCacheConfirm: "Zwischenspeicher zurücksetzen",
|
||||
resetAllSettings: "Alle Einstellungen zurücksetzen",
|
||||
resetAllSettingsHint:
|
||||
"Dadurch werden alle Einstellungen auf ihre Standardwerte zurückgesetzt. Du verlierst alle Einstellungen, die du geändert hast.",
|
||||
resetAllSettingsButton: "Zurücksetzen",
|
||||
resetAllSettingsDialogTitle: "Bist du sicher?",
|
||||
resetAllSettingsDialogContent:
|
||||
|
@ -53,7 +53,7 @@ export const FavoritesList = ({ invokeSearchResultItem }: FavoritesListProps) =>
|
||||
style={{ width: "100%" }}
|
||||
src={getImageUrl({
|
||||
image: searchResultItem.image,
|
||||
onDarkBackground: contextBridge.themeShouldUseDarkColors(),
|
||||
shouldPreferDarkColors: contextBridge.themeShouldUseDarkColors(),
|
||||
})}
|
||||
alt="App Name Document"
|
||||
/>
|
||||
|
@ -103,7 +103,7 @@ export const SearchResultListItem = ({
|
||||
}}
|
||||
src={getImageUrl({
|
||||
image: searchResultItem.image,
|
||||
onDarkBackground: contextBridge.themeShouldUseDarkColors(),
|
||||
shouldPreferDarkColors: contextBridge.themeShouldUseDarkColors(),
|
||||
})}
|
||||
/>
|
||||
</div>
|
||||
|
@ -58,7 +58,7 @@ export const Navigation = ({ settingsPages, enabledExtensions }: NavigationProps
|
||||
style={{ maxWidth: "100%", maxHeight: "100%" }}
|
||||
src={getImageUrl({
|
||||
image: e.image,
|
||||
onDarkBackground: contextBridge.themeShouldUseDarkColors(),
|
||||
shouldPreferDarkColors: contextBridge.themeShouldUseDarkColors(),
|
||||
})}
|
||||
/>
|
||||
</div>
|
||||
|
@ -1,73 +0,0 @@
|
||||
import {
|
||||
Button,
|
||||
Dialog,
|
||||
DialogActions,
|
||||
DialogBody,
|
||||
DialogContent,
|
||||
DialogSurface,
|
||||
DialogTitle,
|
||||
DialogTrigger,
|
||||
Field,
|
||||
} from "@fluentui/react-components";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useContextBridge, useTheme } from "../../Hooks";
|
||||
import { Section } from "../Section";
|
||||
import { SectionList } from "../SectionList";
|
||||
|
||||
export const Debug = () => {
|
||||
const { t } = useTranslation();
|
||||
const ns = "settingsDebug";
|
||||
const { contextBridge } = useContextBridge();
|
||||
const { theme } = useTheme();
|
||||
|
||||
return (
|
||||
<SectionList>
|
||||
<Section>
|
||||
<Field label={t("resetAllSettings", { ns })}>
|
||||
<Dialog>
|
||||
<DialogTrigger disableButtonEnhancement>
|
||||
<div>
|
||||
<Button>{t("resetAllSettingsButton", { ns })}</Button>
|
||||
</div>
|
||||
</DialogTrigger>
|
||||
<DialogSurface>
|
||||
<DialogBody>
|
||||
<DialogTitle>{t("resetAllSettingsDialogTitle", { ns })}</DialogTitle>
|
||||
<DialogContent>{t("resetAllSettingsDialogContent", { ns })}</DialogContent>
|
||||
<DialogActions>
|
||||
<DialogTrigger disableButtonEnhancement>
|
||||
<Button appearance="secondary">{t("resetAllSettingsCancel", { ns })}</Button>
|
||||
</DialogTrigger>
|
||||
<Button onClick={() => contextBridge.resetAllSettings()} appearance="primary">
|
||||
{t("resetAllSettingsConfirm", { ns })}
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</DialogBody>
|
||||
</DialogSurface>
|
||||
</Dialog>
|
||||
</Field>
|
||||
</Section>
|
||||
<Section>
|
||||
<Field label="Logs">
|
||||
<textarea
|
||||
id="logs"
|
||||
readOnly
|
||||
value={contextBridge.getLogs().join("\n\n")}
|
||||
style={{
|
||||
height: 150,
|
||||
width: "100%",
|
||||
fontFamily: theme.fontFamilyMonospace,
|
||||
fontSize: theme.fontSizeBase200,
|
||||
resize: "vertical",
|
||||
background: theme.colorNeutralBackground1,
|
||||
color: theme.colorNeutralForeground1,
|
||||
borderRadius: theme.borderRadiusMedium,
|
||||
padding: 10,
|
||||
boxSizing: "border-box",
|
||||
}}
|
||||
/>
|
||||
</Field>
|
||||
</Section>
|
||||
</SectionList>
|
||||
);
|
||||
};
|
21
src/renderer/Core/Settings/Pages/Debug/Debug.tsx
Normal file
21
src/renderer/Core/Settings/Pages/Debug/Debug.tsx
Normal file
@ -0,0 +1,21 @@
|
||||
import { Section } from "../../Section";
|
||||
import { SectionList } from "../../SectionList";
|
||||
import { Logs } from "./Logs";
|
||||
import { ResetCache } from "./ResetCache";
|
||||
import { ResetSettings } from "./ResetSettings";
|
||||
|
||||
export const Debug = () => {
|
||||
return (
|
||||
<SectionList>
|
||||
<Section>
|
||||
<ResetCache />
|
||||
</Section>
|
||||
<Section>
|
||||
<ResetSettings />
|
||||
</Section>
|
||||
<Section>
|
||||
<Logs />
|
||||
</Section>
|
||||
</SectionList>
|
||||
);
|
||||
};
|
38
src/renderer/Core/Settings/Pages/Debug/Logs.tsx
Normal file
38
src/renderer/Core/Settings/Pages/Debug/Logs.tsx
Normal file
@ -0,0 +1,38 @@
|
||||
import { useContextBridge, useTheme } from "@Core/Hooks";
|
||||
import { Field } from "@fluentui/react-components";
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
export const Logs = () => {
|
||||
const { contextBridge } = useContextBridge();
|
||||
const { theme } = useTheme();
|
||||
|
||||
const [logs, setLogs] = useState<string[]>(contextBridge.getLogs());
|
||||
|
||||
useEffect(() => {
|
||||
contextBridge.ipcRenderer.on("logsUpdated", () => {
|
||||
setLogs(contextBridge.getLogs());
|
||||
});
|
||||
});
|
||||
|
||||
return (
|
||||
<Field label="Logs">
|
||||
<textarea
|
||||
id="logs"
|
||||
readOnly
|
||||
value={logs.join("\n\n")}
|
||||
style={{
|
||||
height: 150,
|
||||
width: "100%",
|
||||
fontFamily: theme.fontFamilyMonospace,
|
||||
fontSize: theme.fontSizeBase200,
|
||||
resize: "vertical",
|
||||
background: theme.colorNeutralBackground1,
|
||||
color: theme.colorNeutralForeground1,
|
||||
borderRadius: theme.borderRadiusMedium,
|
||||
padding: 10,
|
||||
boxSizing: "border-box",
|
||||
}}
|
||||
/>
|
||||
</Field>
|
||||
);
|
||||
};
|
83
src/renderer/Core/Settings/Pages/Debug/ResetCache.tsx
Normal file
83
src/renderer/Core/Settings/Pages/Debug/ResetCache.tsx
Normal file
@ -0,0 +1,83 @@
|
||||
import { useContextBridge } from "@Core/Hooks";
|
||||
import {
|
||||
Button,
|
||||
Dialog,
|
||||
DialogActions,
|
||||
DialogBody,
|
||||
DialogContent,
|
||||
DialogSurface,
|
||||
DialogTitle,
|
||||
DialogTrigger,
|
||||
Field,
|
||||
Spinner,
|
||||
} from "@fluentui/react-components";
|
||||
import { useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
export const ResetCache = () => {
|
||||
const { t } = useTranslation();
|
||||
const ns = "settingsDebug";
|
||||
const { contextBridge } = useContextBridge();
|
||||
const [dialogIsOpen, setDialogIsOpen] = useState<boolean>(false);
|
||||
const [resetIsRunning, setResetIsRunning] = useState<boolean>(false);
|
||||
|
||||
const resetChache = async () => {
|
||||
setResetIsRunning(true);
|
||||
|
||||
try {
|
||||
await contextBridge.resetChache();
|
||||
} catch (error) {
|
||||
// do nothing
|
||||
} finally {
|
||||
setResetIsRunning(false);
|
||||
setDialogIsOpen(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Field label={t("resetCache", { ns })} hint={t("resetCacheHint", { ns })}>
|
||||
<Dialog open={dialogIsOpen}>
|
||||
<DialogTrigger disableButtonEnhancement>
|
||||
<div>
|
||||
<Button onClick={() => setDialogIsOpen(true)}>{t("resetCache", { ns })}</Button>
|
||||
</div>
|
||||
</DialogTrigger>
|
||||
<DialogSurface>
|
||||
<DialogBody>
|
||||
<DialogTitle>
|
||||
{resetIsRunning ? t("resettingCache", { ns }) : t("resetCacheDialogTitle", { ns })}
|
||||
</DialogTitle>
|
||||
<DialogContent>
|
||||
{resetIsRunning ? (
|
||||
<div
|
||||
style={{
|
||||
height: 100,
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
}}
|
||||
>
|
||||
<Spinner />
|
||||
</div>
|
||||
) : (
|
||||
t("resetCacheDialogContent", { ns })
|
||||
)}
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button
|
||||
disabled={resetIsRunning}
|
||||
appearance="secondary"
|
||||
onClick={() => setDialogIsOpen(false)}
|
||||
>
|
||||
{t("resetCacheCancel", { ns })}
|
||||
</Button>
|
||||
<Button disabled={resetIsRunning} onClick={() => resetChache()} appearance="primary">
|
||||
{t("resetCacheConfirm", { ns })}
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</DialogBody>
|
||||
</DialogSurface>
|
||||
</Dialog>
|
||||
</Field>
|
||||
);
|
||||
};
|
45
src/renderer/Core/Settings/Pages/Debug/ResetSettings.tsx
Normal file
45
src/renderer/Core/Settings/Pages/Debug/ResetSettings.tsx
Normal file
@ -0,0 +1,45 @@
|
||||
import { useContextBridge } from "@Core/Hooks";
|
||||
import {
|
||||
Button,
|
||||
Dialog,
|
||||
DialogActions,
|
||||
DialogBody,
|
||||
DialogContent,
|
||||
DialogSurface,
|
||||
DialogTitle,
|
||||
DialogTrigger,
|
||||
Field,
|
||||
} from "@fluentui/react-components";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
export const ResetSettings = () => {
|
||||
const { t } = useTranslation();
|
||||
const ns = "settingsDebug";
|
||||
const { contextBridge } = useContextBridge();
|
||||
|
||||
return (
|
||||
<Field label={t("resetAllSettings", { ns })} hint={t("resetAllSettingsHint", { ns })}>
|
||||
<Dialog>
|
||||
<DialogTrigger disableButtonEnhancement>
|
||||
<div>
|
||||
<Button>{t("resetAllSettingsButton", { ns })}</Button>
|
||||
</div>
|
||||
</DialogTrigger>
|
||||
<DialogSurface>
|
||||
<DialogBody>
|
||||
<DialogTitle>{t("resetAllSettingsDialogTitle", { ns })}</DialogTitle>
|
||||
<DialogContent>{t("resetAllSettingsDialogContent", { ns })}</DialogContent>
|
||||
<DialogActions>
|
||||
<DialogTrigger disableButtonEnhancement>
|
||||
<Button appearance="secondary">{t("resetAllSettingsCancel", { ns })}</Button>
|
||||
</DialogTrigger>
|
||||
<Button onClick={() => contextBridge.resetAllSettings()} appearance="primary">
|
||||
{t("resetAllSettingsConfirm", { ns })}
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</DialogBody>
|
||||
</DialogSurface>
|
||||
</Dialog>
|
||||
</Field>
|
||||
);
|
||||
};
|
1
src/renderer/Core/Settings/Pages/Debug/index.ts
Normal file
1
src/renderer/Core/Settings/Pages/Debug/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export * from "./Debug";
|
@ -101,7 +101,8 @@ export const Extensions = () => {
|
||||
style={{ maxWidth: "100%", maxHeight: "100%" }}
|
||||
src={getImageUrl({
|
||||
image,
|
||||
onDarkBackground: contextBridge.themeShouldUseDarkColors(),
|
||||
shouldPreferDarkColors:
|
||||
contextBridge.themeShouldUseDarkColors(),
|
||||
})}
|
||||
/>
|
||||
</div>
|
||||
|
@ -50,7 +50,7 @@ export const Favorites = () => {
|
||||
style={{ maxWidth: "100%", maxHeight: "100%" }}
|
||||
src={getImageUrl({
|
||||
image: s.image,
|
||||
onDarkBackground: contextBridge.themeShouldUseDarkColors(),
|
||||
shouldPreferDarkColors: contextBridge.themeShouldUseDarkColors(),
|
||||
})}
|
||||
/>
|
||||
</div>
|
||||
|
@ -99,7 +99,7 @@ export const SearchEngine = () => {
|
||||
style={{ width: 16, height: 16 }}
|
||||
src={getImageUrl({
|
||||
image,
|
||||
onDarkBackground: contextBridge.themeShouldUseDarkColors(),
|
||||
shouldPreferDarkColors: contextBridge.themeShouldUseDarkColors(),
|
||||
})}
|
||||
/>
|
||||
}
|
||||
|
@ -6,7 +6,7 @@ describe(getImageUrl, () => {
|
||||
expect(
|
||||
getImageUrl({
|
||||
image: { url: "neutral", urlOnDarkBackground: "dark", urlOnLightBackground: "light" },
|
||||
onDarkBackground: true,
|
||||
shouldPreferDarkColors: true,
|
||||
}),
|
||||
).toBe("dark");
|
||||
});
|
||||
@ -15,7 +15,7 @@ describe(getImageUrl, () => {
|
||||
expect(
|
||||
getImageUrl({
|
||||
image: { url: "neutral", urlOnDarkBackground: "dark", urlOnLightBackground: "light" },
|
||||
onDarkBackground: false,
|
||||
shouldPreferDarkColors: false,
|
||||
}),
|
||||
).toBe("light");
|
||||
});
|
||||
@ -24,7 +24,7 @@ describe(getImageUrl, () => {
|
||||
expect(
|
||||
getImageUrl({
|
||||
image: { url: "neutral", urlOnLightBackground: "light" },
|
||||
onDarkBackground: true,
|
||||
shouldPreferDarkColors: true,
|
||||
}),
|
||||
).toBe("neutral");
|
||||
});
|
||||
@ -33,7 +33,7 @@ describe(getImageUrl, () => {
|
||||
expect(
|
||||
getImageUrl({
|
||||
image: { url: "neutral", urlOnDarkBackground: "dark" },
|
||||
onDarkBackground: false,
|
||||
shouldPreferDarkColors: false,
|
||||
}),
|
||||
).toBe("neutral");
|
||||
});
|
||||
|
@ -1,5 +1,5 @@
|
||||
import type { Image } from "@common/Core/Image";
|
||||
|
||||
export const getImageUrl = ({ image, onDarkBackground }: { image: Image; onDarkBackground: boolean }) => {
|
||||
return (onDarkBackground ? image.urlOnDarkBackground : image.urlOnLightBackground) ?? image.url;
|
||||
export const getImageUrl = ({ image, shouldPreferDarkColors }: { image: Image; shouldPreferDarkColors: boolean }) => {
|
||||
return shouldPreferDarkColors ? image.urlOnDarkBackground ?? image.url : image.urlOnLightBackground ?? image.url;
|
||||
};
|
||||
|
@ -49,7 +49,7 @@ export const DeeplTranslator = ({ contextBridge, goBack }: ExtensionProps) => {
|
||||
alt="DeepL Logo"
|
||||
src={getImageUrl({
|
||||
image: contextBridge.getExtension(extensionId).image,
|
||||
onDarkBackground: contextBridge.themeShouldUseDarkColors(),
|
||||
shouldPreferDarkColors: contextBridge.themeShouldUseDarkColors(),
|
||||
})}
|
||||
style={{ width: 24 }}
|
||||
/>
|
||||
|
@ -69,7 +69,8 @@ export const ShortcutsSettings = () => {
|
||||
style={{ maxWidth: "100%", maxHeight: "100%" }}
|
||||
src={getImageUrl({
|
||||
image: searchResultItem.image,
|
||||
onDarkBackground: contextBridge.themeShouldUseDarkColors(),
|
||||
shouldPreferDarkColors:
|
||||
contextBridge.themeShouldUseDarkColors(),
|
||||
})}
|
||||
/>
|
||||
</div>
|
||||
|
Loading…
Reference in New Issue
Block a user