|
@@ -8,32 +8,36 @@ import { GoLogo } from "./logo"
|
|
|
import { BgPulse, type BgPulseMask } from "./bg-pulse"
|
|
import { BgPulse, type BgPulseMask } from "./bg-pulse"
|
|
|
import { useBindings } from "../keymap"
|
|
import { useBindings } from "../keymap"
|
|
|
|
|
|
|
|
-const GO_URL = "https://opencode.ai/go"
|
|
|
|
|
const PAD_X = 3
|
|
const PAD_X = 3
|
|
|
const PAD_TOP_OUTER = 1
|
|
const PAD_TOP_OUTER = 1
|
|
|
|
|
|
|
|
-export type DialogGoUpsellProps = {
|
|
|
|
|
|
|
+export type DialogRetryActionProps = {
|
|
|
|
|
+ title: string
|
|
|
|
|
+ message: string
|
|
|
|
|
+ label: string
|
|
|
|
|
+ link?: string
|
|
|
onClose?: (dontShowAgain?: boolean) => void
|
|
onClose?: (dontShowAgain?: boolean) => void
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-function subscribe(props: DialogGoUpsellProps, dialog: ReturnType<typeof useDialog>) {
|
|
|
|
|
- open(GO_URL).catch(() => {})
|
|
|
|
|
|
|
+function runAction(props: DialogRetryActionProps, dialog: ReturnType<typeof useDialog>) {
|
|
|
|
|
+ if (props.link) open(props.link).catch(() => {})
|
|
|
props.onClose?.()
|
|
props.onClose?.()
|
|
|
dialog.clear()
|
|
dialog.clear()
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-function dismiss(props: DialogGoUpsellProps, dialog: ReturnType<typeof useDialog>) {
|
|
|
|
|
|
|
+function dismiss(props: DialogRetryActionProps, dialog: ReturnType<typeof useDialog>) {
|
|
|
props.onClose?.(true)
|
|
props.onClose?.(true)
|
|
|
dialog.clear()
|
|
dialog.clear()
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-export function DialogGoUpsell(props: DialogGoUpsellProps) {
|
|
|
|
|
|
|
+export function DialogRetryAction(props: DialogRetryActionProps) {
|
|
|
const dialog = useDialog()
|
|
const dialog = useDialog()
|
|
|
const { theme } = useTheme()
|
|
const { theme } = useTheme()
|
|
|
const fg = selectedForeground(theme)
|
|
const fg = selectedForeground(theme)
|
|
|
- const [selected, setSelected] = createSignal<"dismiss" | "subscribe">("subscribe")
|
|
|
|
|
|
|
+ const [selected, setSelected] = createSignal<"dismiss" | "action">("action")
|
|
|
const [center, setCenter] = createSignal<{ x: number; y: number } | undefined>()
|
|
const [center, setCenter] = createSignal<{ x: number; y: number } | undefined>()
|
|
|
const [masks, setMasks] = createSignal<BgPulseMask[]>([])
|
|
const [masks, setMasks] = createSignal<BgPulseMask[]>([])
|
|
|
|
|
+ const showGoTreatment = () => props.link === "https://opencode.ai/go"
|
|
|
let content: BoxRenderable | undefined
|
|
let content: BoxRenderable | undefined
|
|
|
let logoBox: BoxRenderable | undefined
|
|
let logoBox: BoxRenderable | undefined
|
|
|
let headingBox: BoxRenderable | undefined
|
|
let headingBox: BoxRenderable | undefined
|
|
@@ -41,11 +45,13 @@ export function DialogGoUpsell(props: DialogGoUpsellProps) {
|
|
|
let buttonsBox: BoxRenderable | undefined
|
|
let buttonsBox: BoxRenderable | undefined
|
|
|
|
|
|
|
|
const sync = () => {
|
|
const sync = () => {
|
|
|
- if (!content || !logoBox) return
|
|
|
|
|
- setCenter({
|
|
|
|
|
- x: logoBox.x - content.x + logoBox.width / 2,
|
|
|
|
|
- y: logoBox.y - content.y + logoBox.height / 2 + PAD_TOP_OUTER,
|
|
|
|
|
- })
|
|
|
|
|
|
|
+ if (!content) return
|
|
|
|
|
+ if (logoBox) {
|
|
|
|
|
+ setCenter({
|
|
|
|
|
+ x: logoBox.x - content.x + logoBox.width / 2,
|
|
|
|
|
+ y: logoBox.y - content.y + logoBox.height / 2 + PAD_TOP_OUTER,
|
|
|
|
|
+ })
|
|
|
|
|
+ }
|
|
|
const next: BgPulseMask[] = []
|
|
const next: BgPulseMask[] = []
|
|
|
const baseY = PAD_TOP_OUTER
|
|
const baseY = PAD_TOP_OUTER
|
|
|
for (const b of [headingBox, descBox, buttonsBox]) {
|
|
for (const b of [headingBox, descBox, buttonsBox]) {
|
|
@@ -75,20 +81,20 @@ export function DialogGoUpsell(props: DialogGoUpsellProps) {
|
|
|
bindings: [
|
|
bindings: [
|
|
|
{
|
|
{
|
|
|
key: "left",
|
|
key: "left",
|
|
|
- cmd: () => setSelected((value) => (value === "subscribe" ? "dismiss" : "subscribe")),
|
|
|
|
|
|
|
+ cmd: () => setSelected((value) => (value === "action" ? "dismiss" : "action")),
|
|
|
},
|
|
},
|
|
|
{
|
|
{
|
|
|
key: "right",
|
|
key: "right",
|
|
|
- cmd: () => setSelected((value) => (value === "subscribe" ? "dismiss" : "subscribe")),
|
|
|
|
|
|
|
+ cmd: () => setSelected((value) => (value === "action" ? "dismiss" : "action")),
|
|
|
},
|
|
},
|
|
|
{
|
|
{
|
|
|
key: "tab",
|
|
key: "tab",
|
|
|
- cmd: () => setSelected((value) => (value === "subscribe" ? "dismiss" : "subscribe")),
|
|
|
|
|
|
|
+ cmd: () => setSelected((value) => (value === "action" ? "dismiss" : "action")),
|
|
|
},
|
|
},
|
|
|
{
|
|
{
|
|
|
key: "return",
|
|
key: "return",
|
|
|
cmd: () => {
|
|
cmd: () => {
|
|
|
- if (selected() === "subscribe") subscribe(props, dialog)
|
|
|
|
|
|
|
+ if (selected() === "action") runAction(props, dialog)
|
|
|
else dismiss(props, dialog)
|
|
else dismiss(props, dialog)
|
|
|
},
|
|
},
|
|
|
},
|
|
},
|
|
@@ -97,33 +103,34 @@ export function DialogGoUpsell(props: DialogGoUpsellProps) {
|
|
|
|
|
|
|
|
return (
|
|
return (
|
|
|
<box ref={(item: BoxRenderable) => (content = item)}>
|
|
<box ref={(item: BoxRenderable) => (content = item)}>
|
|
|
- <box position="absolute" top={-PAD_TOP_OUTER} left={0} right={0} bottom={0} zIndex={0}>
|
|
|
|
|
- <BgPulse centerX={center()?.x} centerY={center()?.y} masks={masks()} />
|
|
|
|
|
- </box>
|
|
|
|
|
- <box paddingLeft={PAD_X} paddingRight={PAD_X} paddingBottom={1} gap={1}>
|
|
|
|
|
|
|
+ {showGoTreatment() ? (
|
|
|
|
|
+ <box position="absolute" top={-PAD_TOP_OUTER} left={0} right={0} bottom={0} zIndex={0}>
|
|
|
|
|
+ <BgPulse centerX={center()?.x} centerY={center()?.y} masks={masks()} />
|
|
|
|
|
+ </box>
|
|
|
|
|
+ ) : null}
|
|
|
|
|
+ <box paddingLeft={PAD_X} paddingRight={PAD_X} paddingBottom={1} gap={1} zIndex={1}>
|
|
|
<box ref={(item: BoxRenderable) => (headingBox = item)} flexDirection="row" justifyContent="space-between">
|
|
<box ref={(item: BoxRenderable) => (headingBox = item)} flexDirection="row" justifyContent="space-between">
|
|
|
<text attributes={TextAttributes.BOLD} fg={theme.text}>
|
|
<text attributes={TextAttributes.BOLD} fg={theme.text}>
|
|
|
- Free limit reached
|
|
|
|
|
|
|
+ {props.title}
|
|
|
</text>
|
|
</text>
|
|
|
<text fg={theme.textMuted} onMouseUp={() => dialog.clear()}>
|
|
<text fg={theme.textMuted} onMouseUp={() => dialog.clear()}>
|
|
|
esc
|
|
esc
|
|
|
</text>
|
|
</text>
|
|
|
</box>
|
|
</box>
|
|
|
<box ref={(item: BoxRenderable) => (descBox = item)} gap={0}>
|
|
<box ref={(item: BoxRenderable) => (descBox = item)} gap={0}>
|
|
|
- <box flexDirection="row">
|
|
|
|
|
- <text fg={theme.textMuted}>Subscribe to </text>
|
|
|
|
|
- <text attributes={TextAttributes.BOLD} fg={theme.textMuted}>
|
|
|
|
|
- OpenCode Go
|
|
|
|
|
- </text>
|
|
|
|
|
- <text fg={theme.textMuted}> for reliable access to the</text>
|
|
|
|
|
- </box>
|
|
|
|
|
- <text fg={theme.textMuted}>best open-source models, starting at $5/month.</text>
|
|
|
|
|
|
|
+ <text fg={theme.textMuted}>{props.message}</text>
|
|
|
</box>
|
|
</box>
|
|
|
- <box alignItems="center" gap={1} paddingBottom={1}>
|
|
|
|
|
- <box ref={(item: BoxRenderable) => (logoBox = item)}>
|
|
|
|
|
- <GoLogo />
|
|
|
|
|
- </box>
|
|
|
|
|
- <Link href={GO_URL} fg={theme.primary} />
|
|
|
|
|
|
|
+ <box gap={1} paddingBottom={1}>
|
|
|
|
|
+ {showGoTreatment() ? (
|
|
|
|
|
+ <box ref={(item: BoxRenderable) => (logoBox = item)} alignItems="center">
|
|
|
|
|
+ <GoLogo />
|
|
|
|
|
+ </box>
|
|
|
|
|
+ ) : null}
|
|
|
|
|
+ {props.link ? (
|
|
|
|
|
+ <box width="100%" flexDirection="row" justifyContent="center">
|
|
|
|
|
+ <Link href={props.link} fg={theme.primary} wrapMode="none" />
|
|
|
|
|
+ </box>
|
|
|
|
|
+ ) : null}
|
|
|
</box>
|
|
</box>
|
|
|
<box ref={(item: BoxRenderable) => (buttonsBox = item)} flexDirection="row" justifyContent="space-between">
|
|
<box ref={(item: BoxRenderable) => (buttonsBox = item)} flexDirection="row" justifyContent="space-between">
|
|
|
<box
|
|
<box
|
|
@@ -143,15 +150,15 @@ export function DialogGoUpsell(props: DialogGoUpsellProps) {
|
|
|
<box
|
|
<box
|
|
|
paddingLeft={2}
|
|
paddingLeft={2}
|
|
|
paddingRight={2}
|
|
paddingRight={2}
|
|
|
- backgroundColor={selected() === "subscribe" ? theme.primary : RGBA.fromInts(0, 0, 0, 0)}
|
|
|
|
|
- onMouseOver={() => setSelected("subscribe")}
|
|
|
|
|
- onMouseUp={() => subscribe(props, dialog)}
|
|
|
|
|
|
|
+ backgroundColor={selected() === "action" ? theme.primary : RGBA.fromInts(0, 0, 0, 0)}
|
|
|
|
|
+ onMouseOver={() => setSelected("action")}
|
|
|
|
|
+ onMouseUp={() => runAction(props, dialog)}
|
|
|
>
|
|
>
|
|
|
<text
|
|
<text
|
|
|
- fg={selected() === "subscribe" ? fg : theme.text}
|
|
|
|
|
- attributes={selected() === "subscribe" ? TextAttributes.BOLD : undefined}
|
|
|
|
|
|
|
+ fg={selected() === "action" ? fg : theme.text}
|
|
|
|
|
+ attributes={selected() === "action" ? TextAttributes.BOLD : undefined}
|
|
|
>
|
|
>
|
|
|
- subscribe
|
|
|
|
|
|
|
+ {props.label}
|
|
|
</text>
|
|
</text>
|
|
|
</box>
|
|
</box>
|
|
|
</box>
|
|
</box>
|
|
@@ -160,10 +167,13 @@ export function DialogGoUpsell(props: DialogGoUpsellProps) {
|
|
|
)
|
|
)
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-DialogGoUpsell.show = (dialog: DialogContext) => {
|
|
|
|
|
|
|
+DialogRetryAction.show = (
|
|
|
|
|
+ dialog: DialogContext,
|
|
|
|
|
+ props: Pick<DialogRetryActionProps, "title" | "message" | "label" | "link">,
|
|
|
|
|
+) => {
|
|
|
return new Promise<boolean>((resolve) => {
|
|
return new Promise<boolean>((resolve) => {
|
|
|
dialog.replace(
|
|
dialog.replace(
|
|
|
- () => <DialogGoUpsell onClose={(dontShow) => resolve(dontShow ?? false)} />,
|
|
|
|
|
|
|
+ () => <DialogRetryAction {...props} onClose={(dontShow) => resolve(dontShow ?? false)} />,
|
|
|
() => resolve(false),
|
|
() => resolve(false),
|
|
|
)
|
|
)
|
|
|
})
|
|
})
|