support browser icons
This commit is contained in:
34
src/App.vue
34
src/App.vue
@@ -1,5 +1,6 @@
|
||||
<script setup lang="ts">
|
||||
import SortDropdown from "./components/SortDropdown.vue";
|
||||
import { browserIconOptions, browserIconSrc } from "./features/browser-assistant/icons";
|
||||
import { useBrowserAssistant } from "./features/browser-assistant/useBrowserAssistant";
|
||||
|
||||
const {
|
||||
@@ -61,7 +62,14 @@ const {
|
||||
type="button"
|
||||
@click="selectedBrowserId = browser.browserId; page = 'browserData'"
|
||||
>
|
||||
<div class="browser-nav-icon">{{ browserMonogram(browser.browserId) }}</div>
|
||||
<div class="browser-nav-icon">
|
||||
<img
|
||||
v-if="browserIconSrc(browser.iconKey ?? browser.browserFamilyId)"
|
||||
:src="browserIconSrc(browser.iconKey ?? browser.browserFamilyId) ?? undefined"
|
||||
:alt="`${browser.browserName} icon`"
|
||||
/>
|
||||
<span v-else>{{ browserMonogram(browser.browserId) }}</span>
|
||||
</div>
|
||||
<div class="browser-nav-body">
|
||||
<strong>{{ browser.browserName }}</strong>
|
||||
<span>{{ browser.dataRoot }}</span>
|
||||
@@ -109,6 +117,22 @@ const {
|
||||
<span>Name</span>
|
||||
<input v-model="createConfigForm.name" placeholder="Work Chrome" />
|
||||
</label>
|
||||
<label class="field-group">
|
||||
<span>Icon</span>
|
||||
<div class="icon-option-grid">
|
||||
<button
|
||||
v-for="option in browserIconOptions"
|
||||
:key="option.key"
|
||||
class="icon-option-button"
|
||||
:class="{ active: createConfigForm.iconKey === option.key }"
|
||||
type="button"
|
||||
@click="createConfigForm.iconKey = option.key"
|
||||
>
|
||||
<img :src="option.src" :alt="option.label" />
|
||||
<span>{{ option.label }}</span>
|
||||
</button>
|
||||
</div>
|
||||
</label>
|
||||
<label class="field-group">
|
||||
<span>Executable Path</span>
|
||||
<div class="path-input-row">
|
||||
@@ -166,7 +190,12 @@ const {
|
||||
<div class="config-card-header">
|
||||
<div class="config-card-lead">
|
||||
<div class="browser-nav-icon config-icon">
|
||||
{{ configMonogram(config) }}
|
||||
<img
|
||||
v-if="browserIconSrc(config.iconKey ?? config.browserFamilyId)"
|
||||
:src="browserIconSrc(config.iconKey ?? config.browserFamilyId) ?? undefined"
|
||||
:alt="`${config.name} icon`"
|
||||
/>
|
||||
<span v-else>{{ configMonogram(config) }}</span>
|
||||
</div>
|
||||
<div>
|
||||
<div class="config-title-row">
|
||||
@@ -175,7 +204,6 @@ const {
|
||||
{{ config.source === "default" ? "Default" : "Custom" }}
|
||||
</span>
|
||||
</div>
|
||||
<p class="config-id">{{ config.id }}</p>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
|
||||
BIN
src/assets/brave.png
Normal file
BIN
src/assets/brave.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 30 KiB |
BIN
src/assets/google-chrome.png
Normal file
BIN
src/assets/google-chrome.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 38 KiB |
BIN
src/assets/microsoft-edge.png
Normal file
BIN
src/assets/microsoft-edge.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 88 KiB |
13
src/features/browser-assistant/icons.ts
Normal file
13
src/features/browser-assistant/icons.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import braveIcon from "../../assets/brave.png";
|
||||
import chromeIcon from "../../assets/google-chrome.png";
|
||||
import edgeIcon from "../../assets/microsoft-edge.png";
|
||||
|
||||
export const browserIconOptions = [
|
||||
{ key: "chrome", label: "Google Chrome", src: chromeIcon },
|
||||
{ key: "edge", label: "Microsoft Edge", src: edgeIcon },
|
||||
{ key: "brave", label: "Brave", src: braveIcon },
|
||||
] as const;
|
||||
|
||||
export function browserIconSrc(iconKey: string | null | undefined) {
|
||||
return browserIconOptions.find((option) => option.key === iconKey)?.src ?? null;
|
||||
}
|
||||
@@ -38,6 +38,7 @@ export type BrowserConfigEntry = {
|
||||
id: string;
|
||||
source: BrowserConfigSource;
|
||||
browserFamilyId: string | null;
|
||||
iconKey: string | null;
|
||||
name: string;
|
||||
executablePath: string;
|
||||
userDataPath: string;
|
||||
@@ -50,6 +51,7 @@ export type BrowserConfigListResponse = {
|
||||
|
||||
export type CreateCustomBrowserConfigInput = {
|
||||
name: string;
|
||||
iconKey: string | null;
|
||||
executablePath: string;
|
||||
userDataPath: string;
|
||||
};
|
||||
@@ -58,6 +60,7 @@ export type BrowserView = {
|
||||
browserId: string;
|
||||
browserFamilyId: string | null;
|
||||
browserName: string;
|
||||
iconKey: string | null;
|
||||
dataRoot: string;
|
||||
profiles: ProfileSummary[];
|
||||
extensions: ExtensionSummary[];
|
||||
|
||||
@@ -30,6 +30,7 @@ export function useBrowserAssistant() {
|
||||
const deletingConfigId = ref("");
|
||||
const createConfigForm = ref<CreateCustomBrowserConfigInput>({
|
||||
name: "",
|
||||
iconKey: "chrome",
|
||||
executablePath: "",
|
||||
userDataPath: "",
|
||||
});
|
||||
@@ -150,6 +151,7 @@ export function useBrowserAssistant() {
|
||||
browserConfigs.value = result.configs;
|
||||
createConfigForm.value = {
|
||||
name: "",
|
||||
iconKey: "chrome",
|
||||
executablePath: "",
|
||||
userDataPath: "",
|
||||
};
|
||||
@@ -218,10 +220,10 @@ export function useBrowserAssistant() {
|
||||
|
||||
function browserMonogram(browserId: string) {
|
||||
const current = browsers.value.find((browser) => browser.browserId === browserId);
|
||||
const familyId = current?.browserFamilyId;
|
||||
if (familyId === "chrome") return "CH";
|
||||
if (familyId === "edge") return "ED";
|
||||
if (familyId === "brave") return "BR";
|
||||
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) {
|
||||
@@ -237,9 +239,10 @@ export function useBrowserAssistant() {
|
||||
}
|
||||
|
||||
function configMonogram(config: BrowserConfigEntry) {
|
||||
if (config.browserFamilyId === "chrome") return "CH";
|
||||
if (config.browserFamilyId === "edge") return "ED";
|
||||
if (config.browserFamilyId === "brave") return "BR";
|
||||
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()
|
||||
|
||||
@@ -222,6 +222,13 @@ button {
|
||||
background: linear-gradient(135deg, #10213f, #365f9f);
|
||||
}
|
||||
|
||||
.browser-nav-icon img,
|
||||
.config-icon img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.browser-nav-item.chrome .browser-nav-icon {
|
||||
--accent: var(--chrome);
|
||||
}
|
||||
@@ -315,7 +322,7 @@ button {
|
||||
.config-form-card,
|
||||
.config-card {
|
||||
border-radius: 18px;
|
||||
padding: 14px;
|
||||
padding: 12px;
|
||||
border: 1px solid rgba(148, 163, 184, 0.18);
|
||||
background: var(--panel-strong);
|
||||
}
|
||||
@@ -323,22 +330,61 @@ button {
|
||||
.config-form-header h3,
|
||||
.config-title-row h4 {
|
||||
margin: 0;
|
||||
font-size: 0.98rem;
|
||||
font-size: 0.94rem;
|
||||
}
|
||||
|
||||
.config-form-header p,
|
||||
.config-id,
|
||||
.config-meta-row p {
|
||||
margin: 6px 0 0;
|
||||
color: var(--muted);
|
||||
font-size: 0.86rem;
|
||||
font-size: 0.82rem;
|
||||
}
|
||||
|
||||
.config-form-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
gap: 12px;
|
||||
margin-top: 14px;
|
||||
gap: 10px;
|
||||
margin-top: 12px;
|
||||
}
|
||||
|
||||
.icon-option-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.icon-option-button {
|
||||
display: grid;
|
||||
justify-items: center;
|
||||
gap: 6px;
|
||||
padding: 10px 8px;
|
||||
border: 1px solid rgba(148, 163, 184, 0.2);
|
||||
border-radius: 12px;
|
||||
background: rgba(255, 255, 255, 0.9);
|
||||
color: var(--text);
|
||||
cursor: pointer;
|
||||
transition:
|
||||
border-color 160ms ease,
|
||||
background 160ms ease,
|
||||
box-shadow 160ms ease;
|
||||
}
|
||||
|
||||
.icon-option-button img {
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
.icon-option-button span {
|
||||
font-size: 0.76rem;
|
||||
font-weight: 600;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.icon-option-button.active {
|
||||
border-color: rgba(47, 111, 237, 0.3);
|
||||
background: rgba(232, 240, 255, 0.8);
|
||||
box-shadow: 0 0 0 3px rgba(47, 111, 237, 0.1);
|
||||
}
|
||||
|
||||
.field-group {
|
||||
@@ -361,7 +407,7 @@ button {
|
||||
|
||||
.field-group input {
|
||||
width: 100%;
|
||||
padding: 10px 12px;
|
||||
padding: 9px 11px;
|
||||
border: 1px solid rgba(148, 163, 184, 0.24);
|
||||
border-radius: 12px;
|
||||
background: rgba(255, 255, 255, 0.94);
|
||||
@@ -381,7 +427,7 @@ button {
|
||||
.config-form-actions {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
margin-top: 14px;
|
||||
margin-top: 12px;
|
||||
}
|
||||
|
||||
.primary-button,
|
||||
@@ -423,32 +469,32 @@ button {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.config-card-lead {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 12px;
|
||||
gap: 10px;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.config-icon {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border-radius: 12px;
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
border-radius: 11px;
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
|
||||
.config-meta {
|
||||
display: grid;
|
||||
gap: 10px;
|
||||
margin-top: 12px;
|
||||
gap: 8px;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.config-meta-row {
|
||||
display: grid;
|
||||
gap: 4px;
|
||||
gap: 3px;
|
||||
}
|
||||
|
||||
.config-meta-row p {
|
||||
@@ -861,6 +907,10 @@ button {
|
||||
grid-column: auto;
|
||||
}
|
||||
|
||||
.icon-option-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.sort-bar {
|
||||
justify-content: stretch;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user