fix: persist settings in Electron by using fixed port (#415)

- Use fixed port 61337 in production instead of random ports (10000-65535)
- localStorage is origin-specific, so random ports caused settings loss
- Add locale save/restore since language is URL-based
- Fixes #399
This commit is contained in:
Dayuan Jiang
2025-12-25 22:20:59 +09:00
committed by GitHub
parent c5a04c9e50
commit 8c736cee0d
3 changed files with 43 additions and 36 deletions

View File

@@ -1,4 +1,5 @@
"use client" "use client"
import { usePathname, useRouter } from "next/navigation"
import { useCallback, useEffect, useRef, useState } from "react" import { useCallback, useEffect, useRef, useState } from "react"
import { DrawIoEmbed } from "react-drawio" import { DrawIoEmbed } from "react-drawio"
import type { ImperativePanelHandle } from "react-resizable-panels" import type { ImperativePanelHandle } from "react-resizable-panels"
@@ -10,6 +11,7 @@ import {
ResizablePanelGroup, ResizablePanelGroup,
} from "@/components/ui/resizable" } from "@/components/ui/resizable"
import { useDiagram } from "@/contexts/diagram-context" import { useDiagram } from "@/contexts/diagram-context"
import { i18n, type Locale } from "@/lib/i18n/config"
const drawioBaseUrl = const drawioBaseUrl =
process.env.NEXT_PUBLIC_DRAWIO_BASE_URL || "https://embed.diagrams.net" process.env.NEXT_PUBLIC_DRAWIO_BASE_URL || "https://embed.diagrams.net"
@@ -24,6 +26,8 @@ export default function Home() {
showSaveDialog, showSaveDialog,
setShowSaveDialog, setShowSaveDialog,
} = useDiagram() } = useDiagram()
const router = useRouter()
const pathname = usePathname()
const [isMobile, setIsMobile] = useState(false) const [isMobile, setIsMobile] = useState(false)
const [isChatVisible, setIsChatVisible] = useState(true) const [isChatVisible, setIsChatVisible] = useState(true)
const [drawioUi, setDrawioUi] = useState<"min" | "sketch">("min") const [drawioUi, setDrawioUi] = useState<"min" | "sketch">("min")
@@ -58,6 +62,18 @@ export default function Home() {
// Load preferences from localStorage after mount // Load preferences from localStorage after mount
useEffect(() => { useEffect(() => {
// Restore saved locale and redirect if needed
const savedLocale = localStorage.getItem("next-ai-draw-io-locale")
if (savedLocale && i18n.locales.includes(savedLocale as Locale)) {
const pathParts = pathname.split("/").filter(Boolean)
const currentLocale = pathParts[0]
if (currentLocale !== savedLocale) {
pathParts[0] = savedLocale
router.replace(`/${pathParts.join("/")}`)
return // Wait for redirect
}
}
const savedUi = localStorage.getItem("drawio-theme") const savedUi = localStorage.getItem("drawio-theme")
if (savedUi === "min" || savedUi === "sketch") { if (savedUi === "min" || savedUi === "sketch") {
setDrawioUi(savedUi) setDrawioUi(savedUi)
@@ -84,7 +100,7 @@ export default function Home() {
} }
setIsLoaded(true) setIsLoaded(true)
}, []) }, [pathname, router])
const handleDarkModeChange = async () => { const handleDarkModeChange = async () => {
await saveDiagramToStorage() await saveDiagramToStorage()

View File

@@ -151,6 +151,9 @@ function SettingsContent({
}, [open]) }, [open])
const changeLanguage = (lang: string) => { const changeLanguage = (lang: string) => {
// Save locale to localStorage for persistence across restarts
localStorage.setItem("next-ai-draw-io-locale", lang)
const parts = pathname.split("/") const parts = pathname.split("/")
if (parts.length > 1 && i18n.locales.includes(parts[1] as Locale)) { if (parts.length > 1 && i18n.locales.includes(parts[1] as Locale)) {
parts[1] = lang parts[1] = lang

View File

@@ -3,16 +3,16 @@ import { app } from "electron"
/** /**
* Port configuration * Port configuration
* Using fixed ports to preserve localStorage across restarts
* (localStorage is origin-specific, so changing ports loses all saved data)
*/ */
const PORT_CONFIG = { const PORT_CONFIG = {
// Development mode uses fixed port for hot reload compatibility // Development mode uses fixed port for hot reload compatibility
development: 6002, development: 6002,
// Production mode port range (will find first available) // Production mode uses fixed port (61337) to preserve localStorage
production: { // Falls back to sequential ports if unavailable
min: 10000, production: 61337,
max: 65535, // Maximum attempts to find an available port (fallback)
},
// Maximum attempts to find an available port
maxAttempts: 100, maxAttempts: 100,
} }
@@ -36,19 +36,11 @@ export function isPortAvailable(port: number): Promise<boolean> {
}) })
} }
/**
* Generate a random port within the production range
*/
function getRandomPort(): number {
const { min, max } = PORT_CONFIG.production
return Math.floor(Math.random() * (max - min + 1)) + min
}
/** /**
* Find an available port * Find an available port
* - In development: uses fixed port (6002) * - In development: uses fixed port (6002)
* - In production: finds a random available port * - In production: uses fixed port (61337) to preserve localStorage
* - If a port was previously allocated, verifies it's still available * - Falls back to sequential ports if preferred port is unavailable
* *
* @param reuseExisting If true, try to reuse the previously allocated port * @param reuseExisting If true, try to reuse the previously allocated port
* @returns Promise<number> The available port * @returns Promise<number> The available port
@@ -56,6 +48,9 @@ function getRandomPort(): number {
*/ */
export async function findAvailablePort(reuseExisting = true): Promise<number> { export async function findAvailablePort(reuseExisting = true): Promise<number> {
const isDev = !app.isPackaged const isDev = !app.isPackaged
const preferredPort = isDev
? PORT_CONFIG.development
: PORT_CONFIG.production
// Try to reuse cached port if requested and available // Try to reuse cached port if requested and available
if (reuseExisting && allocatedPort !== null) { if (reuseExisting && allocatedPort !== null) {
@@ -69,29 +64,22 @@ export async function findAvailablePort(reuseExisting = true): Promise<number> {
allocatedPort = null allocatedPort = null
} }
if (isDev) { // Try preferred port first
// Development mode: use fixed port if (await isPortAvailable(preferredPort)) {
const port = PORT_CONFIG.development allocatedPort = preferredPort
const available = await isPortAvailable(port) return preferredPort
if (available) {
allocatedPort = port
return port
}
console.warn(
`Development port ${port} is in use, finding alternative...`,
)
} }
// Production mode or dev port unavailable: find random available port console.warn(
for (let attempt = 0; attempt < PORT_CONFIG.maxAttempts; attempt++) { `Preferred port ${preferredPort} is in use, finding alternative...`,
const port = isDev )
? PORT_CONFIG.development + attempt + 1
: getRandomPort()
const available = await isPortAvailable(port) // Fallback: try sequential ports starting from preferred + 1
if (available) { for (let attempt = 1; attempt <= PORT_CONFIG.maxAttempts; attempt++) {
const port = preferredPort + attempt
if (await isPortAvailable(port)) {
allocatedPort = port allocatedPort = port
console.log(`Allocated port: ${port}`) console.log(`Allocated fallback port: ${port}`)
return port return port
} }
} }