refactor frontend
This commit is contained in:
337
src/composables/useBrowserManager.ts
Normal file
337
src/composables/useBrowserManager.ts
Normal file
@@ -0,0 +1,337 @@
|
||||
import { computed, onMounted, ref, watch } from "vue";
|
||||
import { invoke } from "@tauri-apps/api/core";
|
||||
import { open } from "@tauri-apps/plugin-dialog";
|
||||
|
||||
import { sortBookmarks, sortExtensions, sortProfiles } from "../utils/sort";
|
||||
import type {
|
||||
ActiveSection,
|
||||
AppPage,
|
||||
BookmarkSortKey,
|
||||
BrowserConfigEntry,
|
||||
BrowserConfigListResponse,
|
||||
BrowserView,
|
||||
CreateCustomBrowserConfigInput,
|
||||
ExtensionSortKey,
|
||||
ProfileSortKey,
|
||||
ScanResponse,
|
||||
} from "../types/browser";
|
||||
|
||||
export function useBrowserManager() {
|
||||
const page = ref<AppPage>("browserData");
|
||||
const loading = ref(true);
|
||||
const error = ref("");
|
||||
const openProfileError = ref("");
|
||||
const openingProfileKey = ref("");
|
||||
const response = ref<ScanResponse>({ browsers: [] });
|
||||
const browserConfigs = ref<BrowserConfigEntry[]>([]);
|
||||
const configsLoading = ref(true);
|
||||
const configError = ref("");
|
||||
const savingConfig = ref(false);
|
||||
const deletingConfigId = ref("");
|
||||
const createConfigForm = ref<CreateCustomBrowserConfigInput>({
|
||||
name: "",
|
||||
iconKey: "chrome",
|
||||
executablePath: "",
|
||||
userDataPath: "",
|
||||
});
|
||||
const selectedBrowserId = ref("");
|
||||
const activeSection = ref<ActiveSection>("profiles");
|
||||
const expandedExtensionIds = ref<string[]>([]);
|
||||
const expandedBookmarkUrls = ref<string[]>([]);
|
||||
const profileSortKey = ref<ProfileSortKey>("name");
|
||||
const extensionSortKey = ref<ExtensionSortKey>("name");
|
||||
const bookmarkSortKey = ref<BookmarkSortKey>("title");
|
||||
|
||||
const browsers = computed(() => response.value.browsers);
|
||||
const currentBrowser = computed<BrowserView | null>(
|
||||
() =>
|
||||
browsers.value.find((browser) => browser.browserId === selectedBrowserId.value) ??
|
||||
browsers.value[0] ??
|
||||
null,
|
||||
);
|
||||
|
||||
const sortedProfiles = computed(() =>
|
||||
sortProfiles(currentBrowser.value?.profiles ?? [], profileSortKey.value),
|
||||
);
|
||||
const sortedExtensions = computed(() =>
|
||||
sortExtensions(currentBrowser.value?.extensions ?? [], extensionSortKey.value),
|
||||
);
|
||||
const sortedBookmarks = computed(() =>
|
||||
sortBookmarks(currentBrowser.value?.bookmarks ?? [], bookmarkSortKey.value),
|
||||
);
|
||||
|
||||
watch(
|
||||
browsers,
|
||||
(items) => {
|
||||
if (!items.length) {
|
||||
selectedBrowserId.value = "";
|
||||
return;
|
||||
}
|
||||
|
||||
const hasSelected = items.some(
|
||||
(browser) => browser.browserId === selectedBrowserId.value,
|
||||
);
|
||||
|
||||
if (!hasSelected) {
|
||||
selectedBrowserId.value = items[0].browserId;
|
||||
}
|
||||
},
|
||||
{ immediate: true },
|
||||
);
|
||||
|
||||
watch(selectedBrowserId, () => {
|
||||
expandedExtensionIds.value = [];
|
||||
expandedBookmarkUrls.value = [];
|
||||
openProfileError.value = "";
|
||||
});
|
||||
|
||||
async function loadBrowserConfigs() {
|
||||
configsLoading.value = true;
|
||||
configError.value = "";
|
||||
|
||||
try {
|
||||
const result = await invoke<BrowserConfigListResponse>("list_browser_configs");
|
||||
browserConfigs.value = result.configs;
|
||||
} catch (loadError) {
|
||||
configError.value =
|
||||
loadError instanceof Error ? loadError.message : "Failed to load browser configs.";
|
||||
} finally {
|
||||
configsLoading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
async function scanBrowsers() {
|
||||
loading.value = true;
|
||||
error.value = "";
|
||||
|
||||
try {
|
||||
response.value = await invoke<ScanResponse>("scan_browsers");
|
||||
} catch (scanError) {
|
||||
error.value =
|
||||
scanError instanceof Error
|
||||
? scanError.message
|
||||
: "Failed to scan browser data.";
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
async function refreshAll() {
|
||||
await Promise.all([loadBrowserConfigs(), scanBrowsers()]);
|
||||
}
|
||||
|
||||
async function openBrowserProfile(browserId: string, profileId: string) {
|
||||
const profileKey = `${browserId}:${profileId}`;
|
||||
openingProfileKey.value = profileKey;
|
||||
openProfileError.value = "";
|
||||
|
||||
try {
|
||||
await invoke("open_browser_profile", {
|
||||
browserId,
|
||||
profileId,
|
||||
});
|
||||
} catch (openError) {
|
||||
openProfileError.value =
|
||||
openError instanceof Error
|
||||
? openError.message
|
||||
: "Failed to open the selected browser profile.";
|
||||
} finally {
|
||||
openingProfileKey.value = "";
|
||||
}
|
||||
}
|
||||
|
||||
async function createCustomBrowserConfig() {
|
||||
savingConfig.value = true;
|
||||
configError.value = "";
|
||||
|
||||
try {
|
||||
const result = await invoke<BrowserConfigListResponse>("create_custom_browser_config", {
|
||||
input: createConfigForm.value,
|
||||
});
|
||||
browserConfigs.value = result.configs;
|
||||
createConfigForm.value = {
|
||||
name: "",
|
||||
iconKey: "chrome",
|
||||
executablePath: "",
|
||||
userDataPath: "",
|
||||
};
|
||||
await scanBrowsers();
|
||||
} catch (saveError) {
|
||||
configError.value =
|
||||
saveError instanceof Error ? saveError.message : "Failed to create browser config.";
|
||||
} finally {
|
||||
savingConfig.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
async function deleteCustomBrowserConfig(configId: string) {
|
||||
deletingConfigId.value = configId;
|
||||
configError.value = "";
|
||||
|
||||
try {
|
||||
const result = await invoke<BrowserConfigListResponse>("delete_custom_browser_config", {
|
||||
configId,
|
||||
});
|
||||
browserConfigs.value = result.configs;
|
||||
await scanBrowsers();
|
||||
} catch (deleteError) {
|
||||
configError.value =
|
||||
deleteError instanceof Error ? deleteError.message : "Failed to delete browser config.";
|
||||
} finally {
|
||||
deletingConfigId.value = "";
|
||||
}
|
||||
}
|
||||
|
||||
async function pickExecutablePath() {
|
||||
const selected = await open({
|
||||
multiple: false,
|
||||
directory: false,
|
||||
filters: [
|
||||
{
|
||||
name: "Executable",
|
||||
extensions: ["exe"],
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
if (typeof selected === "string") {
|
||||
createConfigForm.value.executablePath = selected;
|
||||
}
|
||||
}
|
||||
|
||||
async function pickUserDataPath() {
|
||||
const selected = await open({
|
||||
multiple: false,
|
||||
directory: true,
|
||||
});
|
||||
|
||||
if (typeof selected === "string") {
|
||||
createConfigForm.value.userDataPath = selected;
|
||||
}
|
||||
}
|
||||
|
||||
function isDeletingConfig(configId: string) {
|
||||
return deletingConfigId.value === configId;
|
||||
}
|
||||
|
||||
function isOpeningProfile(browserId: string, profileId: string) {
|
||||
return openingProfileKey.value === `${browserId}:${profileId}`;
|
||||
}
|
||||
|
||||
function browserMonogram(browserId: string) {
|
||||
const current = browsers.value.find((browser) => browser.browserId === browserId);
|
||||
const iconKey = current?.iconKey ?? current?.browserFamilyId;
|
||||
if (iconKey === "chrome") return "CH";
|
||||
if (iconKey === "edge") return "ED";
|
||||
if (iconKey === "brave") return "BR";
|
||||
|
||||
const name = current?.browserName?.trim() ?? "";
|
||||
if (name) {
|
||||
const letters = name
|
||||
.split(/\s+/)
|
||||
.filter(Boolean)
|
||||
.slice(0, 2)
|
||||
.map((part) => part[0]);
|
||||
if (letters.length) return letters.join("").toUpperCase();
|
||||
}
|
||||
|
||||
return browserId.slice(0, 2).toUpperCase();
|
||||
}
|
||||
|
||||
function configMonogram(config: BrowserConfigEntry) {
|
||||
const iconKey = config.iconKey ?? config.browserFamilyId;
|
||||
if (iconKey === "chrome") return "CH";
|
||||
if (iconKey === "edge") return "ED";
|
||||
if (iconKey === "brave") return "BR";
|
||||
|
||||
const letters = config.name
|
||||
.trim()
|
||||
.split(/\s+/)
|
||||
.filter(Boolean)
|
||||
.slice(0, 2)
|
||||
.map((part) => part[0]);
|
||||
return (letters.join("") || config.id.slice(0, 2)).toUpperCase();
|
||||
}
|
||||
|
||||
function extensionMonogram(name: string) {
|
||||
return name.trim().slice(0, 1).toUpperCase() || "?";
|
||||
}
|
||||
|
||||
function domainFromUrl(url: string) {
|
||||
try {
|
||||
return new URL(url).hostname;
|
||||
} catch {
|
||||
return url;
|
||||
}
|
||||
}
|
||||
|
||||
function sectionCount(section: ActiveSection) {
|
||||
if (!currentBrowser.value) return 0;
|
||||
if (section === "profiles") return currentBrowser.value.profiles.length;
|
||||
if (section === "extensions") return currentBrowser.value.extensions.length;
|
||||
return currentBrowser.value.bookmarks.length;
|
||||
}
|
||||
|
||||
function toggleExtensionProfiles(extensionId: string) {
|
||||
expandedExtensionIds.value = expandedExtensionIds.value.includes(extensionId)
|
||||
? expandedExtensionIds.value.filter((id) => id !== extensionId)
|
||||
: [...expandedExtensionIds.value, extensionId];
|
||||
}
|
||||
|
||||
function toggleBookmarkProfiles(url: string) {
|
||||
expandedBookmarkUrls.value = expandedBookmarkUrls.value.includes(url)
|
||||
? expandedBookmarkUrls.value.filter((value) => value !== url)
|
||||
: [...expandedBookmarkUrls.value, url];
|
||||
}
|
||||
|
||||
function extensionProfilesExpanded(extensionId: string) {
|
||||
return expandedExtensionIds.value.includes(extensionId);
|
||||
}
|
||||
|
||||
function bookmarkProfilesExpanded(url: string) {
|
||||
return expandedBookmarkUrls.value.includes(url);
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
void refreshAll();
|
||||
});
|
||||
|
||||
return {
|
||||
activeSection,
|
||||
bookmarkProfilesExpanded,
|
||||
bookmarkSortKey,
|
||||
browserConfigs,
|
||||
browserMonogram,
|
||||
browsers,
|
||||
configError,
|
||||
configMonogram,
|
||||
configsLoading,
|
||||
createConfigForm,
|
||||
createCustomBrowserConfig,
|
||||
currentBrowser,
|
||||
deleteCustomBrowserConfig,
|
||||
domainFromUrl,
|
||||
error,
|
||||
extensionMonogram,
|
||||
extensionProfilesExpanded,
|
||||
extensionSortKey,
|
||||
isDeletingConfig,
|
||||
isOpeningProfile,
|
||||
loading,
|
||||
openProfileError,
|
||||
openBrowserProfile,
|
||||
page,
|
||||
pickExecutablePath,
|
||||
pickUserDataPath,
|
||||
profileSortKey,
|
||||
refreshAll,
|
||||
savingConfig,
|
||||
sectionCount,
|
||||
selectedBrowserId,
|
||||
sortedBookmarks,
|
||||
sortedExtensions,
|
||||
sortedProfiles,
|
||||
toggleBookmarkProfiles,
|
||||
toggleExtensionProfiles,
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user