desktop v2 migration finalising (#36912)
This commit is contained in:
@@ -136,6 +136,9 @@ export async function setupTimeline(
|
|||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
if (settings.newLayoutDesigns === false) {
|
||||||
|
localStorage.setItem("app-version.v1", JSON.stringify({ version: "1.17.20" }))
|
||||||
|
}
|
||||||
}, input.settings ?? {})
|
}, input.settings ?? {})
|
||||||
if (input.locale) {
|
if (input.locale) {
|
||||||
await page.addInitScript((locale) => {
|
await page.addInitScript((locale) => {
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ test("redirects a draft to the legacy new-session route", async ({ page }) => {
|
|||||||
await page.addInitScript(
|
await page.addInitScript(
|
||||||
({ directory, draftID, server }) => {
|
({ directory, draftID, server }) => {
|
||||||
localStorage.setItem("settings.v3", JSON.stringify({ general: { newLayoutDesigns: false } }))
|
localStorage.setItem("settings.v3", JSON.stringify({ general: { newLayoutDesigns: false } }))
|
||||||
|
localStorage.setItem("app-version.v1", JSON.stringify({ version: "1.17.20" }))
|
||||||
localStorage.setItem(
|
localStorage.setItem(
|
||||||
"opencode.window.browser.dat:tabs",
|
"opencode.window.browser.dat:tabs",
|
||||||
JSON.stringify([{ type: "draft", draftID, server, directory }]),
|
JSON.stringify([{ type: "draft", draftID, server, directory }]),
|
||||||
|
|||||||
@@ -1,13 +1,12 @@
|
|||||||
import { Icon as IconV2 } from "@opencode-ai/ui/v2/icon"
|
import { Icon as IconV2 } from "@opencode-ai/ui/v2/icon"
|
||||||
import { IconButtonV2 } from "@opencode-ai/ui/v2/icon-button-v2"
|
import { IconButtonV2 } from "@opencode-ai/ui/v2/icon-button-v2"
|
||||||
import { createSignal, Show } from "solid-js"
|
import { createSignal, Show } from "solid-js"
|
||||||
import { createStore } from "solid-js/store"
|
|
||||||
import { Drawer, DrawerClose, DrawerContent } from "@/components/ui/drawer"
|
import { Drawer, DrawerClose, DrawerContent } from "@/components/ui/drawer"
|
||||||
import { usePlatform } from "@/context/platform"
|
import { usePlatform } from "@/context/platform"
|
||||||
|
import { useSettings } from "@/context/settings"
|
||||||
import introducingTabsVideo from "@/assets/help/introducing-tabs.mp4"
|
import introducingTabsVideo from "@/assets/help/introducing-tabs.mp4"
|
||||||
import homeImage from "@/assets/help/home.png"
|
import homeImage from "@/assets/help/home.png"
|
||||||
import tabsImage from "@/assets/help/tabs.png"
|
import tabsImage from "@/assets/help/tabs.png"
|
||||||
import { Persist, persisted } from "@/utils/persist"
|
|
||||||
|
|
||||||
const helpIcon = (
|
const helpIcon = (
|
||||||
<svg
|
<svg
|
||||||
@@ -56,15 +55,12 @@ export function HelpButton() {
|
|||||||
|
|
||||||
// can remove this after the tabs rollout has been out for a while
|
// can remove this after the tabs rollout has been out for a while
|
||||||
export function TabsInfoPopup() {
|
export function TabsInfoPopup() {
|
||||||
if (import.meta.env.VITE_OPENCODE_CHANNEL !== "dev") return null
|
const settings = useSettings()
|
||||||
|
|
||||||
const [state, setState] = persisted(Persist.global("tabsInfoPopup"), createStore({ dismissed: false }))
|
|
||||||
// setState({ dismissed: false }) // for testing
|
|
||||||
const [drawerOpen, setDrawerOpen] = createSignal(false)
|
const [drawerOpen, setDrawerOpen] = createSignal(false)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Drawer open={drawerOpen()} onOpenChange={setDrawerOpen} side="right">
|
<Drawer open={drawerOpen()} onOpenChange={setDrawerOpen} side="right">
|
||||||
<Show when={!state.dismissed}>
|
<Show when={settings.general.shouldDisplayTabsToast()}>
|
||||||
<div
|
<div
|
||||||
class="fixed bottom-14 right-5 z-50 h-[240px] w-[192px] rounded-[8px] bg-v2-background-bg-base p-1 shadow-[var(--v2-elevation-floating)]"
|
class="fixed bottom-14 right-5 z-50 h-[240px] w-[192px] rounded-[8px] bg-v2-background-bg-base p-1 shadow-[var(--v2-elevation-floating)]"
|
||||||
aria-label="Introducing Tabs. Organize your work and active sessions with tabs"
|
aria-label="Introducing Tabs. Organize your work and active sessions with tabs"
|
||||||
@@ -73,7 +69,7 @@ export function TabsInfoPopup() {
|
|||||||
type="button"
|
type="button"
|
||||||
aria-label="Dismiss Tabs information"
|
aria-label="Dismiss Tabs information"
|
||||||
class="absolute top-3 right-3 z-10 size-5 flex items-center justify-center rounded-[4px] bg-[rgba(0,0,0,0.4)]"
|
class="absolute top-3 right-3 z-10 size-5 flex items-center justify-center rounded-[4px] bg-[rgba(0,0,0,0.4)]"
|
||||||
onClick={() => setState("dismissed", true)}
|
onClick={settings.general.dismissTabsToast}
|
||||||
>
|
>
|
||||||
<svg
|
<svg
|
||||||
width="16"
|
width="16"
|
||||||
@@ -90,7 +86,7 @@ export function TabsInfoPopup() {
|
|||||||
type="button"
|
type="button"
|
||||||
class="relative block h-[232px] w-[184px] cursor-pointer overflow-hidden rounded-[4px] text-left"
|
class="relative block h-[232px] w-[184px] cursor-pointer overflow-hidden rounded-[4px] text-left"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setState("dismissed", true)
|
settings.general.dismissTabsToast()
|
||||||
setDrawerOpen(true)
|
setDrawerOpen(true)
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -1,10 +1,13 @@
|
|||||||
import { describe, expect, test } from "bun:test"
|
import { describe, expect, test } from "bun:test"
|
||||||
import {
|
import {
|
||||||
|
isAppUpgrade,
|
||||||
layoutTransitionState,
|
layoutTransitionState,
|
||||||
maximumSunsetTimeout,
|
maximumSunsetTimeout,
|
||||||
newLayoutDesignsDefault,
|
newLayoutDesignsDefault,
|
||||||
nextSunsetCheckDelay,
|
nextSunsetCheckDelay,
|
||||||
resolveNewLayoutDesigns,
|
resolveNewLayoutDesigns,
|
||||||
|
shouldDisplayTabsToast,
|
||||||
|
shouldEnableNewLayout,
|
||||||
} from "./settings"
|
} from "./settings"
|
||||||
|
|
||||||
describe("layout transition", () => {
|
describe("layout transition", () => {
|
||||||
@@ -37,4 +40,33 @@ describe("layout transition", () => {
|
|||||||
expect(nextSunsetCheckDelay(10_000, 9_000)).toBe(1_000)
|
expect(nextSunsetCheckDelay(10_000, 9_000)).toBe(1_000)
|
||||||
expect(nextSunsetCheckDelay(9_000, 10_000)).toBe(0)
|
expect(nextSunsetCheckDelay(9_000, 10_000)).toBe(0)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test("enables the new layout when upgrading from 1.17.19 or earlier", () => {
|
||||||
|
expect(shouldEnableNewLayout("v1.17.19", "1.17.20")).toBe(true)
|
||||||
|
expect(shouldEnableNewLayout("1.16.9", "2.0.0")).toBe(true)
|
||||||
|
})
|
||||||
|
|
||||||
|
test("enables the new layout when no previous version was recorded", () => {
|
||||||
|
expect(shouldEnableNewLayout(undefined, "1.17.20")).toBe(true)
|
||||||
|
})
|
||||||
|
|
||||||
|
test("detects upgrades only when a previous version is older", () => {
|
||||||
|
expect(isAppUpgrade("1.17.19", "1.17.20")).toBe(true)
|
||||||
|
expect(isAppUpgrade(undefined, "1.17.20")).toBe(false)
|
||||||
|
expect(isAppUpgrade("1.17.20", "1.17.20")).toBe(false)
|
||||||
|
expect(isAppUpgrade("1.17.21", "1.17.20")).toBe(false)
|
||||||
|
})
|
||||||
|
|
||||||
|
test("shows the tabs toast for upgrades and existing installs without a recorded version", () => {
|
||||||
|
expect(shouldDisplayTabsToast("1.17.19", "1.17.20", false)).toBe(true)
|
||||||
|
expect(shouldDisplayTabsToast(undefined, "1.17.20", true)).toBe(true)
|
||||||
|
expect(shouldDisplayTabsToast(undefined, "1.17.20", false)).toBe(false)
|
||||||
|
})
|
||||||
|
|
||||||
|
test("does not enable the new layout without a qualifying upgrade", () => {
|
||||||
|
expect(shouldEnableNewLayout("1.17.19", "1.17.19")).toBe(false)
|
||||||
|
expect(shouldEnableNewLayout("1.17.20", "1.17.21")).toBe(false)
|
||||||
|
expect(shouldEnableNewLayout(undefined, "1.17.19")).toBe(false)
|
||||||
|
expect(shouldEnableNewLayout("dev", "1.17.20")).toBe(false)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { createStore, reconcile } from "solid-js/store"
|
|||||||
import { createEffect, createMemo, createSignal, onCleanup } from "solid-js"
|
import { createEffect, createMemo, createSignal, onCleanup } from "solid-js"
|
||||||
import { createSimpleContext } from "@opencode-ai/ui/context"
|
import { createSimpleContext } from "@opencode-ai/ui/context"
|
||||||
import { persisted } from "@/utils/persist"
|
import { persisted } from "@/utils/persist"
|
||||||
|
import { usePlatform } from "@/context/platform"
|
||||||
|
|
||||||
export interface NotificationSettings {
|
export interface NotificationSettings {
|
||||||
agent: boolean
|
agent: boolean
|
||||||
@@ -36,6 +37,7 @@ export interface Settings {
|
|||||||
newLayoutDesigns?: boolean
|
newLayoutDesigns?: boolean
|
||||||
layoutTransitionEligible?: boolean
|
layoutTransitionEligible?: boolean
|
||||||
newInterfaceNoticeDismissed?: boolean
|
newInterfaceNoticeDismissed?: boolean
|
||||||
|
shouldDisplayTabsToast?: boolean
|
||||||
}
|
}
|
||||||
appearance: {
|
appearance: {
|
||||||
fontSize: number
|
fontSize: number
|
||||||
@@ -57,7 +59,49 @@ export const terminalDefault = "JetBrainsMono Nerd Font Mono"
|
|||||||
const legacyNewLayoutDesignsDefault = import.meta.env.VITE_OPENCODE_CHANNEL !== "prod"
|
const legacyNewLayoutDesignsDefault = import.meta.env.VITE_OPENCODE_CHANNEL !== "prod"
|
||||||
export const newLayoutDesignsDefault = true
|
export const newLayoutDesignsDefault = true
|
||||||
// Existing users can switch layouts until local midnight on this date. Set new Date(YYYY, M-1, D) to show.
|
// Existing users can switch layouts until local midnight on this date. Set new Date(YYYY, M-1, D) to show.
|
||||||
export const oldInterfaceSunset = import.meta.env.VITE_OPENCODE_CHANNEL !== "prod" ? new Date(2026, 7, 14) : null
|
export const oldInterfaceSunset = new Date(2026, 8, 14)
|
||||||
|
const newLayoutDesignsUpgradeCutoff = "1.17.19"
|
||||||
|
|
||||||
|
function compareVersions(a: string, b: string) {
|
||||||
|
const parse = (version: string) => {
|
||||||
|
const match = /^v?(\d+)\.(\d+)\.(\d+)(?:[-+].*)?$/i.exec(version.trim())
|
||||||
|
if (!match) return
|
||||||
|
return match.slice(1).map(Number)
|
||||||
|
}
|
||||||
|
const left = parse(a)
|
||||||
|
const right = parse(b)
|
||||||
|
if (!left || !right) return
|
||||||
|
const index = left.findIndex((part, index) => part !== right[index])
|
||||||
|
return index === -1 ? 0 : left[index]! - right[index]!
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isAppUpgrade(previous: string | undefined, current: string | undefined) {
|
||||||
|
if (!previous || !current) return false
|
||||||
|
const comparison = compareVersions(current, previous)
|
||||||
|
return comparison !== undefined && comparison > 0
|
||||||
|
}
|
||||||
|
|
||||||
|
export function shouldDisplayTabsToast(
|
||||||
|
previous: string | undefined,
|
||||||
|
current: string | undefined,
|
||||||
|
existingInstall: boolean,
|
||||||
|
) {
|
||||||
|
return isAppUpgrade(previous, current) || (!previous && existingInstall)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function shouldEnableNewLayout(previous: string | undefined, current: string | undefined) {
|
||||||
|
if (!current) return false
|
||||||
|
const currentComparison = compareVersions(current, newLayoutDesignsUpgradeCutoff)
|
||||||
|
if (!previous) return currentComparison !== undefined && currentComparison > 0
|
||||||
|
if (!isAppUpgrade(previous, current)) return false
|
||||||
|
const previousComparison = compareVersions(previous, newLayoutDesignsUpgradeCutoff)
|
||||||
|
return (
|
||||||
|
previousComparison !== undefined &&
|
||||||
|
currentComparison !== undefined &&
|
||||||
|
previousComparison <= 0 &&
|
||||||
|
currentComparison > 0
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
export function layoutTransitionState(scheduled: boolean, eligible: boolean, retired: boolean, dismissed: boolean) {
|
export function layoutTransitionState(scheduled: boolean, eligible: boolean, retired: boolean, dismissed: boolean) {
|
||||||
return {
|
return {
|
||||||
@@ -175,7 +219,17 @@ export const { use: useSettings, provider: SettingsProvider } = createSimpleCont
|
|||||||
name: "Settings",
|
name: "Settings",
|
||||||
gate: false,
|
gate: false,
|
||||||
init: () => {
|
init: () => {
|
||||||
|
const platform = usePlatform()
|
||||||
const [store, setStore, _, ready] = persisted("settings.v3", createStore<Settings>(defaultSettings))
|
const [store, setStore, _, ready] = persisted("settings.v3", createStore<Settings>(defaultSettings))
|
||||||
|
const [launch, setLaunch, , launchReady] = persisted(
|
||||||
|
"app-version.v1",
|
||||||
|
createStore<{ version?: string }>({ version: undefined }),
|
||||||
|
)
|
||||||
|
const [launchState, setLaunchState] = createStore({
|
||||||
|
classified: false,
|
||||||
|
migrationApplied: false,
|
||||||
|
previous: undefined as string | undefined,
|
||||||
|
})
|
||||||
const showFileTree = withFallback(() => store.general?.showFileTree, defaultSettings.general.showFileTree)
|
const showFileTree = withFallback(() => store.general?.showFileTree, defaultSettings.general.showFileTree)
|
||||||
const showSearch = withFallback(() => store.general?.showSearch, defaultSettings.general.showSearch)
|
const showSearch = withFallback(() => store.general?.showSearch, defaultSettings.general.showSearch)
|
||||||
const showStatus = withFallback(() => store.general?.showStatus, defaultSettings.general.showStatus)
|
const showStatus = withFallback(() => store.general?.showStatus, defaultSettings.general.showStatus)
|
||||||
@@ -188,10 +242,16 @@ export const { use: useSettings, provider: SettingsProvider } = createSimpleCont
|
|||||||
const layoutTransitionClassified = createMemo(() => typeof store.general?.layoutTransitionEligible === "boolean")
|
const layoutTransitionClassified = createMemo(() => typeof store.general?.layoutTransitionEligible === "boolean")
|
||||||
const layoutTransitionEligible = withFallback(() => store.general?.layoutTransitionEligible, false)
|
const layoutTransitionEligible = withFallback(() => store.general?.layoutTransitionEligible, false)
|
||||||
const newInterfaceNoticeDismissed = withFallback(() => store.general?.newInterfaceNoticeDismissed, false)
|
const newInterfaceNoticeDismissed = withFallback(() => store.general?.newInterfaceNoticeDismissed, false)
|
||||||
|
const layoutUpgrade = createMemo(() =>
|
||||||
|
launchState.classified && !launchState.migrationApplied
|
||||||
|
? shouldEnableNewLayout(launchState.previous, platform.version)
|
||||||
|
: false,
|
||||||
|
)
|
||||||
const layoutTransition = createMemo(() =>
|
const layoutTransition = createMemo(() =>
|
||||||
layoutTransitionState(!!sunset, layoutTransitionEligible(), oldInterfaceRetired(), newInterfaceNoticeDismissed()),
|
layoutTransitionState(!!sunset, layoutTransitionEligible(), oldInterfaceRetired(), newInterfaceNoticeDismissed()),
|
||||||
)
|
)
|
||||||
const newLayoutDesigns = createMemo(() => {
|
const newLayoutDesigns = createMemo(() => {
|
||||||
|
if (layoutUpgrade()) return true
|
||||||
if (!ready() && !oldInterfaceRetired()) return legacyNewLayoutDesignsDefault
|
if (!ready() && !oldInterfaceRetired()) return legacyNewLayoutDesignsDefault
|
||||||
if (!layoutTransitionClassified()) {
|
if (!layoutTransitionClassified()) {
|
||||||
return resolveNewLayoutDesigns(
|
return resolveNewLayoutDesigns(
|
||||||
@@ -223,6 +283,35 @@ export const { use: useSettings, provider: SettingsProvider } = createSimpleCont
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
createEffect(() => {
|
||||||
|
if (!launchReady() || launchState.classified) return
|
||||||
|
setLaunchState({
|
||||||
|
classified: true,
|
||||||
|
previous: launch.version,
|
||||||
|
})
|
||||||
|
if (!platform.version || launch.version === platform.version) return
|
||||||
|
setLaunch("version", platform.version)
|
||||||
|
})
|
||||||
|
|
||||||
|
createEffect(() => {
|
||||||
|
if (!ready() || !launchState.classified || launchState.migrationApplied) return
|
||||||
|
if (layoutUpgrade() && store.general?.newLayoutDesigns !== true) {
|
||||||
|
setStore("general", "newLayoutDesigns", true)
|
||||||
|
}
|
||||||
|
setLaunchState("migrationApplied", true)
|
||||||
|
})
|
||||||
|
|
||||||
|
createEffect(() => {
|
||||||
|
if (!ready() || !launchState.classified) return
|
||||||
|
if (typeof store.general?.shouldDisplayTabsToast === "boolean") return
|
||||||
|
if (!launchState.previous && !layoutTransitionClassified()) return
|
||||||
|
setStore(
|
||||||
|
"general",
|
||||||
|
"shouldDisplayTabsToast",
|
||||||
|
shouldDisplayTabsToast(launchState.previous, platform.version, layoutTransitionEligible()),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
createEffect(() => {
|
createEffect(() => {
|
||||||
if (!ready() || !oldInterfaceRetired()) return
|
if (!ready() || !oldInterfaceRetired()) return
|
||||||
if (store.general?.newLayoutDesigns === true) return
|
if (store.general?.newLayoutDesigns === true) return
|
||||||
@@ -316,7 +405,10 @@ export const { use: useSettings, provider: SettingsProvider } = createSimpleCont
|
|||||||
},
|
},
|
||||||
newLayoutDesigns,
|
newLayoutDesigns,
|
||||||
setNewLayoutDesigns(value: boolean) {
|
setNewLayoutDesigns(value: boolean) {
|
||||||
setStore("general", "newLayoutDesigns", oldInterfaceRetired() ? true : value)
|
const next = oldInterfaceRetired() ? true : value
|
||||||
|
if (newLayoutDesigns() === next) return
|
||||||
|
setStore("general", "newLayoutDesigns", next)
|
||||||
|
if (typeof window !== "undefined") setTimeout(() => window.location.reload())
|
||||||
},
|
},
|
||||||
layoutTransitionClassified,
|
layoutTransitionClassified,
|
||||||
setOldLayoutEligible(eligible: boolean) {
|
setOldLayoutEligible(eligible: boolean) {
|
||||||
@@ -329,6 +421,10 @@ export const { use: useSettings, provider: SettingsProvider } = createSimpleCont
|
|||||||
dismissNewInterfaceNotice() {
|
dismissNewInterfaceNotice() {
|
||||||
setStore("general", "newInterfaceNoticeDismissed", true)
|
setStore("general", "newInterfaceNoticeDismissed", true)
|
||||||
},
|
},
|
||||||
|
shouldDisplayTabsToast: withFallback(() => store.general?.shouldDisplayTabsToast, false),
|
||||||
|
dismissTabsToast() {
|
||||||
|
setStore("general", "shouldDisplayTabsToast", false)
|
||||||
|
},
|
||||||
},
|
},
|
||||||
visibility: {
|
visibility: {
|
||||||
fileTree: visible(showFileTree),
|
fileTree: visible(showFileTree),
|
||||||
|
|||||||
@@ -208,11 +208,11 @@ export const { use: useTabs, provider: TabsProvider } = createSimpleContext({
|
|||||||
if (!tab || tab.type !== "draft") throw new Error(`Draft not found: ${draftID}`)
|
if (!tab || tab.type !== "draft") throw new Error(`Draft not found: ${draftID}`)
|
||||||
return tab
|
return tab
|
||||||
},
|
},
|
||||||
newDraft(draft: Omit<DraftTab, "type" | "draftID">, prompt?: string, model?: PromptModel) {
|
async newDraft(draft: Omit<DraftTab, "type" | "draftID">, prompt?: string, model?: PromptModel) {
|
||||||
const draftID = uuid()
|
const draftID = uuid()
|
||||||
const tab = { type: "draft" as const, draftID, ...draft }
|
const tab = { type: "draft" as const, draftID, ...draft }
|
||||||
memory.ensure(tabKey(tab), "prompt", () => createDraftPromptSession(draftID, { prompt, model }))
|
memory.ensure(tabKey(tab), "prompt", () => createDraftPromptSession(draftID, { prompt, model }))
|
||||||
void startTransition(() => {
|
await startTransition(() => {
|
||||||
setStore(
|
setStore(
|
||||||
produce((tabs) => {
|
produce((tabs) => {
|
||||||
tabs.push(tab)
|
tabs.push(tab)
|
||||||
@@ -220,6 +220,7 @@ export const { use: useTabs, provider: TabsProvider } = createSimpleContext({
|
|||||||
)
|
)
|
||||||
navigate(draftHref(draftID))
|
navigate(draftHref(draftID))
|
||||||
})
|
})
|
||||||
|
return tab
|
||||||
},
|
},
|
||||||
updateDraft(draftID: string, draft: Partial<Omit<DraftTab, "type" | "draftID">>) {
|
updateDraft(draftID: string, draft: Partial<Omit<DraftTab, "type" | "draftID">>) {
|
||||||
void startTransition(() => {
|
void startTransition(() => {
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import { FIRST_LAUNCH_ONBOARDING_COMPLETE_KEY, OLD_LAYOUT_ELIGIBLE_KEY } from ".
|
|||||||
import { write as writeLog } from "./logging"
|
import { write as writeLog } from "./logging"
|
||||||
import { hasExistingAppState } from "./install-state"
|
import { hasExistingAppState } from "./install-state"
|
||||||
|
|
||||||
const DEFAULT_PROJECT_DIR = "New OpenCode Project"
|
const DEFAULT_PROJECT_DIR = "Default Project"
|
||||||
|
|
||||||
export function initializeOldLayoutEligibility(userDataPath: string) {
|
export function initializeOldLayoutEligibility(userDataPath: string) {
|
||||||
const entries = existsSync(userDataPath) ? readdirSync(userDataPath, { withFileTypes: true }) : []
|
const entries = existsSync(userDataPath) ? readdirSync(userDataPath, { withFileTypes: true }) : []
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ protocol.registerSchemesAsPrivileged([
|
|||||||
secure: true,
|
secure: true,
|
||||||
standard: true,
|
standard: true,
|
||||||
supportFetchAPI: true,
|
supportFetchAPI: true,
|
||||||
|
stream: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
])
|
])
|
||||||
@@ -266,7 +267,10 @@ export function registerRendererProtocol() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await net.fetch(pathToFileURL(file).toString())
|
const range = request.headers.get("range")
|
||||||
|
const response = await net.fetch(pathToFileURL(file).toString(), {
|
||||||
|
headers: range ? { range } : undefined,
|
||||||
|
})
|
||||||
if (response.status >= 400) {
|
if (response.status >= 400) {
|
||||||
writeLog(
|
writeLog(
|
||||||
"protocol",
|
"protocol",
|
||||||
|
|||||||
@@ -1,22 +1,14 @@
|
|||||||
import {
|
import {
|
||||||
ServerConnection,
|
ServerConnection,
|
||||||
useLayout,
|
|
||||||
useProviders,
|
|
||||||
useServer,
|
useServer,
|
||||||
useServerSDK,
|
|
||||||
useServerSync,
|
|
||||||
useSettings,
|
useSettings,
|
||||||
useTabs,
|
useTabs,
|
||||||
} from "@opencode-ai/app"
|
} from "@opencode-ai/app"
|
||||||
import { onMount, startTransition } from "solid-js"
|
import { onMount } from "solid-js"
|
||||||
|
|
||||||
export function DesktopFirstLaunchOnboarding(props: { initialUrl: string; onLoaded: () => void }) {
|
export function DesktopFirstLaunchOnboarding(props: { initialUrl: string; onLoaded: () => void }) {
|
||||||
const server = useServer()
|
const server = useServer()
|
||||||
const serverSDK = useServerSDK()
|
|
||||||
const serverSync = useServerSync()
|
|
||||||
const settings = useSettings()
|
const settings = useSettings()
|
||||||
const layout = useLayout()
|
|
||||||
const providers = useProviders()
|
|
||||||
const tabs = useTabs()
|
const tabs = useTabs()
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
@@ -26,40 +18,26 @@ export function DesktopFirstLaunchOnboarding(props: { initialUrl: string; onLoad
|
|||||||
async function runFirstLaunchOnboarding() {
|
async function runFirstLaunchOnboarding() {
|
||||||
try {
|
try {
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
[server.ready.promise, layout.ready.promise, tabs.ready.promise, tabs.recentReady.promise].map(
|
[server.ready.promise, tabs.ready.promise, tabs.recentReady.promise].map((p) => p ?? Promise.resolve()),
|
||||||
(p) => p ?? Promise.resolve(),
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
settings.general.setOldLayoutEligible(await window.api.isOldLayoutEligible())
|
const existingInstall = await window.api.isOldLayoutEligible()
|
||||||
|
settings.general.setOldLayoutEligible(existingInstall)
|
||||||
if (!server.isLocal()) return
|
if (!server.isLocal()) return
|
||||||
|
|
||||||
const pending = await window.api.isFirstLaunchOnboardingPending()
|
const pending = await window.api.isFirstLaunchOnboardingPending()
|
||||||
if (!pending) return
|
if (!pending) return
|
||||||
|
|
||||||
const sessions = await serverSDK()
|
|
||||||
.client.session.list()
|
|
||||||
.then((x) => x.data ?? [])
|
|
||||||
.catch(() => undefined)
|
|
||||||
const connectedProviders = providers.connected()
|
|
||||||
const paidProviders = providers.paid()
|
|
||||||
const persistedProjects = layout.projects.list()
|
|
||||||
const shouldTrigger =
|
const shouldTrigger =
|
||||||
|
!existingInstall &&
|
||||||
props.initialUrl === "/" &&
|
props.initialUrl === "/" &&
|
||||||
sessions?.length === 0 &&
|
|
||||||
paidProviders.length === 0 &&
|
|
||||||
persistedProjects.length === 0 &&
|
|
||||||
tabs.store.length === 0 &&
|
tabs.store.length === 0 &&
|
||||||
server.list.every(ServerConnection.builtin)
|
server.list.every(ServerConnection.builtin)
|
||||||
|
|
||||||
console.info("[desktop-onboarding] first launch onboarding evaluated", {
|
console.info("[desktop-onboarding] first launch onboarding evaluated", {
|
||||||
pending,
|
pending,
|
||||||
shouldTrigger,
|
shouldTrigger,
|
||||||
|
existingInstall,
|
||||||
initialUrl: props.initialUrl,
|
initialUrl: props.initialUrl,
|
||||||
sessions: sessions?.length,
|
|
||||||
connectedProviders: connectedProviders.length,
|
|
||||||
paidProviders: paidProviders.length,
|
|
||||||
serverProjects: serverSync().data.project.length,
|
|
||||||
persistedProjects: persistedProjects.length,
|
|
||||||
tabs: tabs.store.length,
|
tabs: tabs.store.length,
|
||||||
servers: server.list.map(ServerConnection.key),
|
servers: server.list.map(ServerConnection.key),
|
||||||
})
|
})
|
||||||
@@ -70,9 +48,7 @@ export function DesktopFirstLaunchOnboarding(props: { initialUrl: string; onLoad
|
|||||||
console.info("[desktop-onboarding] starting first launch draft", { directory })
|
console.info("[desktop-onboarding] starting first launch draft", { directory })
|
||||||
server.projects.open(directory)
|
server.projects.open(directory)
|
||||||
server.projects.touch(directory)
|
server.projects.touch(directory)
|
||||||
await startTransition(() => {
|
tabs.select(await tabs.newDraft({ server: server.key, directory }))
|
||||||
tabs.newDraft({ server: server.key, directory })
|
|
||||||
})
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("[desktop-onboarding] first launch onboarding failed", error)
|
console.error("[desktop-onboarding] first launch onboarding failed", error)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user