diff --git a/app.go b/app.go index aab85c61..b52fd7c4 100644 --- a/app.go +++ b/app.go @@ -19,6 +19,7 @@ import ( "solo/internal/configuration" "solo/internal/environment" "solo/internal/exporter" + "solo/internal/fonts" "solo/internal/git" "solo/internal/host" "solo/internal/importer" @@ -375,6 +376,32 @@ func (a *App) GetThemeByName(themeName string) (*theme.Theme, error) { return a.configManager.GetThemeByName(themeName) } +// Font Management Methods + +// SetDefaultFontFamily sets the default font family and persists the change. +func (a *App) SetDefaultFontFamily(fontFamily string) error { + if a.configManager == nil { + return fmt.Errorf("configuration manager not initialized") + } + return a.configManager.SetDefaultFontFamily(fontFamily) +} + +// SetMonoFontFamily sets the monospace font family and persists the change. +func (a *App) SetMonoFontFamily(fontFamily string) error { + if a.configManager == nil { + return fmt.Errorf("configuration manager not initialized") + } + return a.configManager.SetMonoFontFamily(fontFamily) +} + +// SetZoomLevel updates and persists the UI zoom level. +func (a *App) SetZoomLevel(level float64) error { + if a.configManager == nil { + return fmt.Errorf("configuration manager not initialized") + } + return a.configManager.SetZoomLevel(level) +} + // Host Management Methods // GetAllHosts returns a list of all configured hosts. @@ -814,6 +841,11 @@ func (a *App) GetDefaultConfiguration() (configuration.Configuration, error) { return a.configManager.GetDefaultConfiguration(), nil } +// ListSystemFonts returns installed system font families with monospace metadata. +func (a *App) ListSystemFonts(refresh bool) ([]fonts.SystemFont, error) { + return fonts.ListFamilies(refresh) +} + // Request Management Methods // GetRequests returns all requests within a specific collection. diff --git a/docs/releases/next.md b/docs/releases/next.md index fd5a76e3..e1512b1e 100644 --- a/docs/releases/next.md +++ b/docs/releases/next.md @@ -3,10 +3,18 @@ ## Highlights ## ✨ New Features + - Add stop button to cancel an ongoing HTTP request before the timeout expires ([#146](https://github.com/raml-dev/solo/issues/146)). - Add stop button to the parallel runner to cancel an in-progress run ([#152](https://github.com/raml-dev/solo/issues/152)). - Enhanced OpenAPI, Bruno, and Postman importers with full support for query parameters, form bodies, and recursive reference resolution. Introduced smart placeholders for missing examples and standardized binary file handling across all formats ([#148](https://github.com/raml-dev/solo/pull/148)). +- Added UI to change fonts ([#150](https://github.com/raml-dev/solo/pull/150)) ## 🐞 Bug fixes - Fix failing tests on particular conditions by adding a helper to create temp testing folders ([#145](https://github.com/raml-dev/solo/pull/145)) + +## ⬆️ Dependency updates + +- Updated Svelte to `5.55.7` ([#155](https://github.com/raml-dev/solo/pull/155)) +- Updated go to `1.26.3` ([#150](https://github.com/raml-dev/solo/pull/150)) +- Added [golang.org/x/image v0.39.0](https://pkg.go.dev/golang.org/x/image) ([#150](https://github.com/raml-dev/solo/pull/150)) diff --git a/frontend/package-lock.json b/frontend/package-lock.json index d3a63165..4cda780e 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -1561,9 +1561,9 @@ } }, "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { - "version": "5.0.5", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz", - "integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==", + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.6.tgz", + "integrity": "sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==", "dev": true, "license": "MIT", "dependencies": { diff --git a/frontend/package.json.md5 b/frontend/package.json.md5 index 786221da..f298e037 100755 --- a/frontend/package.json.md5 +++ b/frontend/package.json.md5 @@ -1 +1 @@ -5cb7e31dc24be576a4d6e677d2b522a4 \ No newline at end of file +a0d58cdeee5fe9a475016d1d05aad5c9 \ No newline at end of file diff --git a/frontend/src/App.svelte b/frontend/src/App.svelte index 5d5aaff6..53432756 100644 --- a/frontend/src/App.svelte +++ b/frontend/src/App.svelte @@ -13,20 +13,28 @@ import HTTPRequestBuilder from "$src/lib/components/RequestBuilder/HTTPRequestBuilder.svelte"; import RequestTabBar from "$src/lib/components/RequestBuilder/RequestTabBar.svelte"; import { collectionStore } from "$src/lib/stores/collectionStore.svelte"; - import { configurationStore } from "$src/lib/stores/configurationStore.svelte"; + import { + configurationStore, + configurationStoreState + } from "$src/lib/stores/configurationStore.svelte"; import { environmentStore, environmentStoreState } from "$src/lib/stores/environmentStore.svelte"; + import { fontListsStore } from "$src/lib/stores/fontListsStore.svelte"; import { historyStore } from "$src/lib/stores/historyStore.svelte"; import { hasOpenModals, modalStack } from "$src/lib/stores/modalStackStore.svelte"; import { notifications } from "$src/lib/stores/notificationStore"; import { getActiveTab, tabStore } from "$src/lib/stores/tabStore.svelte"; import { updateStore } from "$src/lib/stores/updateStore.svelte"; import { initWindowDimensions } from "$src/lib/stores/windowDimensionsStore.svelte"; - import { initZoom } from "$src/lib/stores/zoomStore.svelte"; + import { initZoom, registerZoomShortcuts } from "$src/lib/stores/zoomStore.svelte"; import { flowbiteTheme } from "$src/lib/theme/flowbiteCustomTheme"; + import { + APP_HISTORY_PANE_MAX_HEIGHT, + APP_HISTORY_PANE_MIN_HEIGHT + } from "$src/lib/utils/constants"; import { EventsOn } from "$wails/runtime/runtime"; + import ClockArrowOutline from "flowbite-svelte-icons/ClockArrowOutline.svelte"; import EditOutline from "flowbite-svelte-icons/EditOutline.svelte"; import GlobeOutline from "flowbite-svelte-icons/GlobeOutline.svelte"; - import ClockArrowOutline from "flowbite-svelte-icons/ClockArrowOutline.svelte"; import Badge from "flowbite-svelte/Badge.svelte"; import Button from "flowbite-svelte/Button.svelte"; import ThemeProvider from "flowbite-svelte/ThemeProvider.svelte"; @@ -40,15 +48,15 @@ const environmentManagerModal = modalStack.createModal("app-environments"); let historyOpen = $state(false); - let consoleHeight = $state(260); - const MIN_HEIGHT = 120; - const MAX_HEIGHT = 700; + let historyPaneHeight = $state(260); let isResizing = $state(false); let resizeStartY = 0; let resizeStartH = 0; async function initializeApp() { await configurationStore.init(); + initZoom(configurationStoreState.config.general.zoomLevel); + void fontListsStore.init(); await Promise.all([ updateStore.init(), collectionStore.loadCollections(), @@ -73,7 +81,7 @@ function startResize(e: MouseEvent) { isResizing = true; resizeStartY = e.clientY; - resizeStartH = consoleHeight; + resizeStartH = historyPaneHeight; window.addEventListener("mousemove", onMouseMove); window.addEventListener("mouseup", stopResize); } @@ -81,7 +89,10 @@ function onMouseMove(e: MouseEvent) { const delta = resizeStartY - e.clientY; - consoleHeight = Math.min(MAX_HEIGHT, Math.max(MIN_HEIGHT, resizeStartH + delta)); + historyPaneHeight = Math.min( + APP_HISTORY_PANE_MAX_HEIGHT, + Math.max(APP_HISTORY_PANE_MIN_HEIGHT, resizeStartH + delta) + ); } function stopResize() { @@ -187,7 +198,7 @@ }); }); - const zoomCleanup = initZoom(); + const zoomCleanup = registerZoomShortcuts(); const windowDimensionsCleanup = initWindowDimensions(); return () => { @@ -260,14 +271,14 @@ {#if historyOpen}
diff --git a/frontend/src/app.css b/frontend/src/app.css index a987c654..db5ede7a 100644 --- a/frontend/src/app.css +++ b/frontend/src/app.css @@ -80,12 +80,15 @@ --color-neutral-900: #18181b; /* typography */ - --font-sans: + --font-sans-default: "Inter", "Nunito", "Segoe UI", Roboto, Ubuntu, Cantarell, "Noto Sans", Arial, sans-serif; - --font-mono: + --font-mono-default: "JetBrains Mono", "Fira Code", "SFMono-Regular", Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; + --font-sans: var(--font-sans-default); + --font-mono: var(--font-mono-default); + /* app shell sizing */ --spacing-sidebar: 280px; --spacing-toolbar: 48px; @@ -162,7 +165,7 @@ body { @apply bg-neutral-50 font-sans text-neutral-900 dark:bg-neutral-900 dark:text-neutral-100; - font-size: 0.875rem; /* 14px global base text */ + font-size: 1rem; margin: 0; overflow: hidden; } diff --git a/frontend/src/assets/styles/codemirror-theme.css b/frontend/src/assets/styles/codemirror-theme.css index 31cef2ab..16b898bf 100644 --- a/frontend/src/assets/styles/codemirror-theme.css +++ b/frontend/src/assets/styles/codemirror-theme.css @@ -11,15 +11,9 @@ font-family: var(--font-mono); } -.cm-scroller, -.cm-content { - font-family: inherit; -} - .cm-scroller { line-height: inherit; overflow-x: auto; - overflow-y: auto; } .cm-content, diff --git a/frontend/src/lib/actions/envAutocomplete.ts b/frontend/src/lib/actions/envAutocomplete.ts index 37fd1637..444fe6cc 100644 --- a/frontend/src/lib/actions/envAutocomplete.ts +++ b/frontend/src/lib/actions/envAutocomplete.ts @@ -3,6 +3,12 @@ * SPDX-License-Identifier: AGPL-3.0-only */ +import { + ENV_AUTOCOMPLETE_DEFAULT_INSERT_MODE, + ENV_AUTOCOMPLETE_DEFAULT_MAX_ITEMS, + ENV_AUTOCOMPLETE_DEFAULT_TRIGGER +} from "$src/lib/utils/constants"; + export interface EnvAutocompleteEntry { key: string; value: string; @@ -39,10 +45,6 @@ interface MatchContext { query: string; } -const DEFAULT_TRIGGER = "{{"; -const DEFAULT_MAX_ITEMS = 8; -const DEFAULT_INSERT_MODE = "value"; - function getCaretCoordinates( node: TextFieldElement, caretIndex: number @@ -131,7 +133,7 @@ export function envAutocomplete(node: TextFieldElement, options: EnvAutocomplete const caret = node.selectionStart; if (caret === null) return null; - const trigger = currentOptions.trigger ?? DEFAULT_TRIGGER; + const trigger = currentOptions.trigger ?? ENV_AUTOCOMPLETE_DEFAULT_TRIGGER; const beforeCaret = node.value.slice(0, caret); const openIndex = beforeCaret.lastIndexOf(trigger); @@ -157,7 +159,7 @@ export function envAutocomplete(node: TextFieldElement, options: EnvAutocomplete filtered = entries .filter((entry) => !query || entry.key.toLowerCase().includes(query)) .sort((a, b) => a.key.localeCompare(b.key)) - .slice(0, currentOptions.maxItems ?? DEFAULT_MAX_ITEMS); + .slice(0, currentOptions.maxItems ?? ENV_AUTOCOMPLETE_DEFAULT_MAX_ITEMS); } function setMenuPosition() { @@ -185,7 +187,7 @@ export function envAutocomplete(node: TextFieldElement, options: EnvAutocomplete function applySelection(entry: EnvAutocompleteEntry) { if (!matchContext) return; - const insertMode = currentOptions.insertMode ?? DEFAULT_INSERT_MODE; + const insertMode = currentOptions.insertMode ?? ENV_AUTOCOMPLETE_DEFAULT_INSERT_MODE; const replacement = insertMode === "token" ? `{{${entry.key}}}` : entry.value; node.setRangeText(replacement, matchContext.start, matchContext.end, "end"); diff --git a/frontend/src/lib/components/Collections/CollectionRow.svelte b/frontend/src/lib/components/Collections/CollectionRow.svelte index 86f88cea..95b397c8 100644 --- a/frontend/src/lib/components/Collections/CollectionRow.svelte +++ b/frontend/src/lib/components/Collections/CollectionRow.svelte @@ -13,6 +13,7 @@ collectionTreeUIState } from "$src/lib/features/collections/collectionTreeUI.svelte"; import { collectionStore } from "$src/lib/stores/collectionStore.svelte"; + import { COLLECTION_OUTLINE_BUTTON_CLASSES } from "$src/lib/utils/constants"; import { clampNumberToMax, getTotalRequestCount } from "$src/lib/utils/helpers"; import { collection } from "$wails/go/models"; import AdjustmentsVerticalOutline from "flowbite-svelte-icons/AdjustmentsVerticalOutline.svelte"; @@ -23,9 +24,6 @@ import { tick } from "svelte"; import { SvelteSet } from "svelte/reactivity"; - const OUTLINE_BUTTON_CLASSES = - "text-neutral-800/70 hover:text-neutral-800 dark:text-neutral-100/70 dark:hover:text-neutral-100"; - interface Props { collection: collection.Collection; expanded: boolean; @@ -291,9 +289,9 @@ aria-label="Toggle collection" > {#if expanded} - + {:else} - + {/if} @@ -331,7 +329,7 @@ type="button" class="h-6 shrink-0 {hasCollectionVariables ? 'text-warning-500 hover:text-warning-600 dark:text-warning-400 dark:hover:text-warning-300' - : OUTLINE_BUTTON_CLASSES}" + : COLLECTION_OUTLINE_BUTTON_CLASSES}" title="Collection variables" aria-label={`Open variables for ${currentCollection.name}`} onclick={(event: MouseEvent) => { @@ -343,7 +341,7 @@ - {/each} -
-
+ void handleThemeSelect(themeId)} + onSansFontChange={(fontFamily) => void handleSansFontChange(fontFamily)} + onMonoFontChange={(fontFamily) => void handleMonoFontChange(fontFamily)} + onZoomLevelChange={(level) => void handleZoomLevelChange(level)} + onRefreshFonts={() => void handleRefreshFonts()} + onResetSansFont={() => void handleSansFontReset()} + onResetMonoFont={() => void handleMonoFontReset()} + onResetZoomLevel={() => void handleZoomLevelReset()} + /> {:else if activeSection === "general"}
diff --git a/frontend/src/lib/components/RequestBuilder/EnvAutocompletePopover.svelte b/frontend/src/lib/components/RequestBuilder/EnvAutocompletePopover.svelte index 9c7926b9..c18494f9 100644 --- a/frontend/src/lib/components/RequestBuilder/EnvAutocompletePopover.svelte +++ b/frontend/src/lib/components/RequestBuilder/EnvAutocompletePopover.svelte @@ -161,7 +161,7 @@ {#if entry.hasConflicts} - {getConflictMessage(entry)} + + + +
+
+

Appearance

+

+ Personalize your experience to match your style. +

+
+ +
+
+

Themes

+

+ Pick how Solo looks and which color scheme it uses. +

+
+ +
+
+ + +
+ +
+ + +
+
+
+ +
+
+

Interface

+

+ Adjust the interface zoom and choose custom fonts. +

+
+ +
+
+ + + onSansFontChange(fontFamily)} + /> + + +
+ +
+ + + onMonoFontChange(fontFamily)} + /> + + +
+
+ +
+ + + + + +
+ + {#if fontsLoading} + Loading system fonts... + {:else if fontsError} + Could not load system fonts. + {/if} + +
+ +
+
+
diff --git a/frontend/src/lib/components/Settings/ColorSchemeSelect.svelte b/frontend/src/lib/components/Settings/ColorSchemeSelect.svelte new file mode 100644 index 00000000..1be6982b --- /dev/null +++ b/frontend/src/lib/components/Settings/ColorSchemeSelect.svelte @@ -0,0 +1,92 @@ + + + + + +
+ {#each themes as currentTheme, index (currentTheme.id)} + + {/each} +
+
diff --git a/frontend/src/lib/components/Settings/FontFamilySelect.svelte b/frontend/src/lib/components/Settings/FontFamilySelect.svelte new file mode 100644 index 00000000..03e14513 --- /dev/null +++ b/frontend/src/lib/components/Settings/FontFamilySelect.svelte @@ -0,0 +1,290 @@ + + + + + +
+ void handleFilterInput()} + onkeydown={(event) => void handleKeydown(event)} + /> + +
+ {#if visibleOptions.length === 0} +

+ No fonts match your search. +

+ {:else} +
+ + {#each renderedOptions as option, localIndex (option.id)} + {@const actualIndex = renderStartIndex + localIndex} + + {/each} + +
+ {/if} +
+
+
diff --git a/frontend/src/lib/components/Settings/ThemeModeSelect.svelte b/frontend/src/lib/components/Settings/ThemeModeSelect.svelte new file mode 100644 index 00000000..273ea3cd --- /dev/null +++ b/frontend/src/lib/components/Settings/ThemeModeSelect.svelte @@ -0,0 +1,67 @@ + + + + + +
+ {#each OPTIONS as option (option.value)} + + {/each} +
+
diff --git a/frontend/src/lib/components/Settings/ZoomLevelSelect.svelte b/frontend/src/lib/components/Settings/ZoomLevelSelect.svelte new file mode 100644 index 00000000..7d56090f --- /dev/null +++ b/frontend/src/lib/components/Settings/ZoomLevelSelect.svelte @@ -0,0 +1,82 @@ + + + + + +
+ {#each options as option, index (option)} + + {/each} +
+
diff --git a/frontend/src/lib/components/UpdateBanner.svelte b/frontend/src/lib/components/UpdateBanner.svelte index a7f87732..215835e9 100644 --- a/frontend/src/lib/components/UpdateBanner.svelte +++ b/frontend/src/lib/components/UpdateBanner.svelte @@ -135,7 +135,10 @@ {#if $topModalId === releaseNotesModal.id} {/if} -
+
{@html releaseNotesHtml}
@@ -160,43 +163,46 @@