mirror of
https://gitea.toothfairyai.com/ToothFairyAI/tf_code.git
synced 2026-04-27 02:54:48 +00:00
chore: cleanup (#17284)
This commit is contained in:
@@ -86,15 +86,17 @@ const useServerHealth = (servers: Accessor<ServerConnection.Any[]>) => {
|
|||||||
const useDefaultServerKey = (
|
const useDefaultServerKey = (
|
||||||
get: (() => string | Promise<string | null | undefined> | null | undefined) | undefined,
|
get: (() => string | Promise<string | null | undefined> | null | undefined) | undefined,
|
||||||
) => {
|
) => {
|
||||||
const [url, setUrl] = createSignal<string | undefined>()
|
const [state, setState] = createStore({
|
||||||
const [tick, setTick] = createSignal(0)
|
url: undefined as string | undefined,
|
||||||
|
tick: 0,
|
||||||
|
})
|
||||||
|
|
||||||
createEffect(() => {
|
createEffect(() => {
|
||||||
tick()
|
state.tick
|
||||||
let dead = false
|
let dead = false
|
||||||
const result = get?.()
|
const result = get?.()
|
||||||
if (!result) {
|
if (!result) {
|
||||||
setUrl(undefined)
|
setState("url", undefined)
|
||||||
onCleanup(() => {
|
onCleanup(() => {
|
||||||
dead = true
|
dead = true
|
||||||
})
|
})
|
||||||
@@ -104,7 +106,7 @@ const useDefaultServerKey = (
|
|||||||
if (result instanceof Promise) {
|
if (result instanceof Promise) {
|
||||||
void result.then((next) => {
|
void result.then((next) => {
|
||||||
if (dead) return
|
if (dead) return
|
||||||
setUrl(next ? normalizeServerUrl(next) : undefined)
|
setState("url", next ? normalizeServerUrl(next) : undefined)
|
||||||
})
|
})
|
||||||
onCleanup(() => {
|
onCleanup(() => {
|
||||||
dead = true
|
dead = true
|
||||||
@@ -112,7 +114,7 @@ const useDefaultServerKey = (
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
setUrl(normalizeServerUrl(result))
|
setState("url", normalizeServerUrl(result))
|
||||||
onCleanup(() => {
|
onCleanup(() => {
|
||||||
dead = true
|
dead = true
|
||||||
})
|
})
|
||||||
@@ -120,11 +122,11 @@ const useDefaultServerKey = (
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
key: () => {
|
key: () => {
|
||||||
const u = url()
|
const u = state.url
|
||||||
if (!u) return
|
if (!u) return
|
||||||
return ServerConnection.key({ type: "http", http: { url: u } })
|
return ServerConnection.key({ type: "http", http: { url: u } })
|
||||||
},
|
},
|
||||||
refresh: () => setTick((value) => value + 1),
|
refresh: () => setState("tick", (value) => value + 1),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,16 +1,4 @@
|
|||||||
import {
|
import { batch, createEffect, createMemo, For, on, onCleanup, onMount, ParentProps, Show, untrack } from "solid-js"
|
||||||
batch,
|
|
||||||
createEffect,
|
|
||||||
createMemo,
|
|
||||||
createSignal,
|
|
||||||
For,
|
|
||||||
on,
|
|
||||||
onCleanup,
|
|
||||||
onMount,
|
|
||||||
ParentProps,
|
|
||||||
Show,
|
|
||||||
untrack,
|
|
||||||
} from "solid-js"
|
|
||||||
import { useNavigate, useParams } from "@solidjs/router"
|
import { useNavigate, useParams } from "@solidjs/router"
|
||||||
import { useLayout, LocalProject } from "@/context/layout"
|
import { useLayout, LocalProject } from "@/context/layout"
|
||||||
import { useGlobalSync } from "@/context/global-sync"
|
import { useGlobalSync } from "@/context/global-sync"
|
||||||
@@ -145,6 +133,10 @@ export default function Layout(props: ParentProps) {
|
|||||||
hoverProject: undefined as string | undefined,
|
hoverProject: undefined as string | undefined,
|
||||||
scrollSessionKey: undefined as string | undefined,
|
scrollSessionKey: undefined as string | undefined,
|
||||||
nav: undefined as HTMLElement | undefined,
|
nav: undefined as HTMLElement | undefined,
|
||||||
|
sortNow: Date.now(),
|
||||||
|
sizing: false,
|
||||||
|
peek: undefined as LocalProject | undefined,
|
||||||
|
peeked: false,
|
||||||
})
|
})
|
||||||
|
|
||||||
const editor = createInlineEditorController()
|
const editor = createInlineEditorController()
|
||||||
@@ -163,14 +155,13 @@ export default function Layout(props: ParentProps) {
|
|||||||
}
|
}
|
||||||
const isBusy = (directory: string) => !!state.busyWorkspaces[workspaceKey(directory)]
|
const isBusy = (directory: string) => !!state.busyWorkspaces[workspaceKey(directory)]
|
||||||
const navLeave = { current: undefined as number | undefined }
|
const navLeave = { current: undefined as number | undefined }
|
||||||
const [sortNow, setSortNow] = createSignal(Date.now())
|
const sortNow = () => state.sortNow
|
||||||
const [sizing, setSizing] = createSignal(false)
|
|
||||||
let sizet: number | undefined
|
let sizet: number | undefined
|
||||||
let sortNowInterval: ReturnType<typeof setInterval> | undefined
|
let sortNowInterval: ReturnType<typeof setInterval> | undefined
|
||||||
const sortNowTimeout = setTimeout(
|
const sortNowTimeout = setTimeout(
|
||||||
() => {
|
() => {
|
||||||
setSortNow(Date.now())
|
setState("sortNow", Date.now())
|
||||||
sortNowInterval = setInterval(() => setSortNow(Date.now()), 60_000)
|
sortNowInterval = setInterval(() => setState("sortNow", Date.now()), 60_000)
|
||||||
},
|
},
|
||||||
60_000 - (Date.now() % 60_000),
|
60_000 - (Date.now() % 60_000),
|
||||||
)
|
)
|
||||||
@@ -196,7 +187,7 @@ export default function Layout(props: ParentProps) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
const stop = () => setSizing(false)
|
const stop = () => setState("sizing", false)
|
||||||
window.addEventListener("pointerup", stop)
|
window.addEventListener("pointerup", stop)
|
||||||
window.addEventListener("pointercancel", stop)
|
window.addEventListener("pointercancel", stop)
|
||||||
window.addEventListener("blur", stop)
|
window.addEventListener("blur", stop)
|
||||||
@@ -234,8 +225,6 @@ export default function Layout(props: ParentProps) {
|
|||||||
}, 300)
|
}, 300)
|
||||||
}
|
}
|
||||||
|
|
||||||
const [peek, setPeek] = createSignal<LocalProject | undefined>(undefined)
|
|
||||||
const [peeked, setPeeked] = createSignal(false)
|
|
||||||
let peekt: number | undefined
|
let peekt: number | undefined
|
||||||
|
|
||||||
const hoverProjectData = createMemo(() => {
|
const hoverProjectData = createMemo(() => {
|
||||||
@@ -251,17 +240,17 @@ export default function Layout(props: ParentProps) {
|
|||||||
clearTimeout(peekt)
|
clearTimeout(peekt)
|
||||||
peekt = undefined
|
peekt = undefined
|
||||||
}
|
}
|
||||||
setPeek(p)
|
setState("peek", p)
|
||||||
setPeeked(true)
|
setState("peeked", true)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
setPeeked(false)
|
setState("peeked", false)
|
||||||
if (peek() === undefined) return
|
if (state.peek === undefined) return
|
||||||
if (peekt !== undefined) clearTimeout(peekt)
|
if (peekt !== undefined) clearTimeout(peekt)
|
||||||
peekt = window.setTimeout(() => {
|
peekt = window.setTimeout(() => {
|
||||||
peekt = undefined
|
peekt = undefined
|
||||||
setPeek(undefined)
|
setState("peek", undefined)
|
||||||
}, 180)
|
}, 180)
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -2245,7 +2234,7 @@ export default function Layout(props: ParentProps) {
|
|||||||
>
|
>
|
||||||
<div class="@container w-full h-full contain-strict">{sidebarContent()}</div>
|
<div class="@container w-full h-full contain-strict">{sidebarContent()}</div>
|
||||||
<Show when={layout.sidebar.opened()}>
|
<Show when={layout.sidebar.opened()}>
|
||||||
<div onPointerDown={() => setSizing(true)}>
|
<div onPointerDown={() => setState("sizing", true)}>
|
||||||
<ResizeHandle
|
<ResizeHandle
|
||||||
direction="horizontal"
|
direction="horizontal"
|
||||||
size={layout.sidebar.width()}
|
size={layout.sidebar.width()}
|
||||||
@@ -2253,9 +2242,9 @@ export default function Layout(props: ParentProps) {
|
|||||||
max={typeof window === "undefined" ? 1000 : window.innerWidth * 0.3 + 64}
|
max={typeof window === "undefined" ? 1000 : window.innerWidth * 0.3 + 64}
|
||||||
collapseThreshold={244}
|
collapseThreshold={244}
|
||||||
onResize={(w) => {
|
onResize={(w) => {
|
||||||
setSizing(true)
|
setState("sizing", true)
|
||||||
if (sizet !== undefined) clearTimeout(sizet)
|
if (sizet !== undefined) clearTimeout(sizet)
|
||||||
sizet = window.setTimeout(() => setSizing(false), 120)
|
sizet = window.setTimeout(() => setState("sizing", false), 120)
|
||||||
layout.sidebar.resize(w)
|
layout.sidebar.resize(w)
|
||||||
}}
|
}}
|
||||||
onCollapse={layout.sidebar.close}
|
onCollapse={layout.sidebar.close}
|
||||||
@@ -2300,7 +2289,7 @@ export default function Layout(props: ParentProps) {
|
|||||||
"xl:inset-y-0 xl:right-0 xl:left-[var(--main-left)]": true,
|
"xl:inset-y-0 xl:right-0 xl:left-[var(--main-left)]": true,
|
||||||
"z-20": true,
|
"z-20": true,
|
||||||
"transition-[left] duration-200 ease-[cubic-bezier(0.22,1,0.36,1)] will-change-[left] motion-reduce:transition-none":
|
"transition-[left] duration-200 ease-[cubic-bezier(0.22,1,0.36,1)] will-change-[left] motion-reduce:transition-none":
|
||||||
!sizing(),
|
!state.sizing,
|
||||||
}}
|
}}
|
||||||
style={{
|
style={{
|
||||||
"--main-left": layout.sidebar.opened() ? `${Math.max(layout.sidebar.width(), 244)}px` : "4rem",
|
"--main-left": layout.sidebar.opened() ? `${Math.max(layout.sidebar.width(), 244)}px` : "4rem",
|
||||||
@@ -2320,11 +2309,11 @@ export default function Layout(props: ParentProps) {
|
|||||||
<div
|
<div
|
||||||
classList={{
|
classList={{
|
||||||
"hidden xl:flex absolute inset-y-0 left-16 z-30": true,
|
"hidden xl:flex absolute inset-y-0 left-16 z-30": true,
|
||||||
"opacity-100 translate-x-0 pointer-events-auto": peeked() && !layout.sidebar.opened(),
|
"opacity-100 translate-x-0 pointer-events-auto": state.peeked && !layout.sidebar.opened(),
|
||||||
"opacity-0 -translate-x-2 pointer-events-none": !peeked() || layout.sidebar.opened(),
|
"opacity-0 -translate-x-2 pointer-events-none": !state.peeked || layout.sidebar.opened(),
|
||||||
"transition-[opacity,transform] motion-reduce:transition-none": true,
|
"transition-[opacity,transform] motion-reduce:transition-none": true,
|
||||||
"duration-180 ease-out": peeked() && !layout.sidebar.opened(),
|
"duration-180 ease-out": state.peeked && !layout.sidebar.opened(),
|
||||||
"duration-120 ease-in": !peeked() || layout.sidebar.opened(),
|
"duration-120 ease-in": !state.peeked || layout.sidebar.opened(),
|
||||||
}}
|
}}
|
||||||
onMouseMove={disarm}
|
onMouseMove={disarm}
|
||||||
onMouseEnter={() => {
|
onMouseEnter={() => {
|
||||||
@@ -2336,19 +2325,19 @@ export default function Layout(props: ParentProps) {
|
|||||||
arm()
|
arm()
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Show when={peek()}>
|
<Show when={state.peek}>
|
||||||
<SidebarPanel project={peek()} merged={false} />
|
<SidebarPanel project={state.peek} merged={false} />
|
||||||
</Show>
|
</Show>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
classList={{
|
classList={{
|
||||||
"hidden xl:block pointer-events-none absolute inset-y-0 right-0 z-25 overflow-hidden": true,
|
"hidden xl:block pointer-events-none absolute inset-y-0 right-0 z-25 overflow-hidden": true,
|
||||||
"opacity-100 translate-x-0": peeked() && !layout.sidebar.opened(),
|
"opacity-100 translate-x-0": state.peeked && !layout.sidebar.opened(),
|
||||||
"opacity-0 -translate-x-2": !peeked() || layout.sidebar.opened(),
|
"opacity-0 -translate-x-2": !state.peeked || layout.sidebar.opened(),
|
||||||
"transition-[opacity,transform] motion-reduce:transition-none": true,
|
"transition-[opacity,transform] motion-reduce:transition-none": true,
|
||||||
"duration-180 ease-out": peeked() && !layout.sidebar.opened(),
|
"duration-180 ease-out": state.peeked && !layout.sidebar.opened(),
|
||||||
"duration-120 ease-in": !peeked() || layout.sidebar.opened(),
|
"duration-120 ease-in": !state.peeked || layout.sidebar.opened(),
|
||||||
}}
|
}}
|
||||||
style={{ left: `calc(4rem + ${Math.max(Math.max(layout.sidebar.width(), 244) - 64, 0)}px)` }}
|
style={{ left: `calc(4rem + ${Math.max(Math.max(layout.sidebar.width(), 244) - 64, 0)}px)` }}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { For, Index, createEffect, createMemo, createSignal, on } from "solid-js"
|
import { For, Index, createEffect, createMemo, on } from "solid-js"
|
||||||
|
import { createStore } from "solid-js/store"
|
||||||
|
|
||||||
const TRACK = Array.from({ length: 30 }, (_, index) => index % 10)
|
const TRACK = Array.from({ length: 30 }, (_, index) => index % 10)
|
||||||
const DURATION = 600
|
const DURATION = 600
|
||||||
@@ -14,8 +15,12 @@ function spin(from: number, to: number, direction: 1 | -1) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function Digit(props: { value: number; direction: 1 | -1 }) {
|
function Digit(props: { value: number; direction: 1 | -1 }) {
|
||||||
const [step, setStep] = createSignal(props.value + 10)
|
const [state, setState] = createStore({
|
||||||
const [animating, setAnimating] = createSignal(false)
|
step: props.value + 10,
|
||||||
|
animating: false,
|
||||||
|
})
|
||||||
|
const step = () => state.step
|
||||||
|
const animating = () => state.animating
|
||||||
let last = props.value
|
let last = props.value
|
||||||
|
|
||||||
createEffect(
|
createEffect(
|
||||||
@@ -25,13 +30,13 @@ function Digit(props: { value: number; direction: 1 | -1 }) {
|
|||||||
const delta = spin(last, next, props.direction)
|
const delta = spin(last, next, props.direction)
|
||||||
last = next
|
last = next
|
||||||
if (!delta) {
|
if (!delta) {
|
||||||
setAnimating(false)
|
setState("animating", false)
|
||||||
setStep(next + 10)
|
setState("step", next + 10)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
setAnimating(true)
|
setState("animating", true)
|
||||||
setStep((value) => value + delta)
|
setState("step", (value) => value + delta)
|
||||||
},
|
},
|
||||||
{ defer: true },
|
{ defer: true },
|
||||||
),
|
),
|
||||||
@@ -43,8 +48,8 @@ function Digit(props: { value: number; direction: 1 | -1 }) {
|
|||||||
data-slot="animated-number-strip"
|
data-slot="animated-number-strip"
|
||||||
data-animating={animating() ? "true" : "false"}
|
data-animating={animating() ? "true" : "false"}
|
||||||
onTransitionEnd={() => {
|
onTransitionEnd={() => {
|
||||||
setAnimating(false)
|
setState("animating", false)
|
||||||
setStep((value) => normalize(value) + 10)
|
setState("step", (value) => normalize(value) + 10)
|
||||||
}}
|
}}
|
||||||
style={{
|
style={{
|
||||||
"--animated-number-offset": `${step()}`,
|
"--animated-number-offset": `${step()}`,
|
||||||
@@ -63,8 +68,12 @@ export function AnimatedNumber(props: { value: number; class?: string }) {
|
|||||||
return Math.max(0, Math.round(props.value))
|
return Math.max(0, Math.round(props.value))
|
||||||
})
|
})
|
||||||
|
|
||||||
const [value, setValue] = createSignal(target())
|
const [state, setState] = createStore({
|
||||||
const [direction, setDirection] = createSignal<1 | -1>(1)
|
value: target(),
|
||||||
|
direction: 1 as 1 | -1,
|
||||||
|
})
|
||||||
|
const value = () => state.value
|
||||||
|
const direction = () => state.direction
|
||||||
|
|
||||||
createEffect(
|
createEffect(
|
||||||
on(
|
on(
|
||||||
@@ -73,8 +82,8 @@ export function AnimatedNumber(props: { value: number; class?: string }) {
|
|||||||
const current = value()
|
const current = value()
|
||||||
if (next === current) return
|
if (next === current) return
|
||||||
|
|
||||||
setDirection(next > current ? 1 : -1)
|
setState("direction", next > current ? 1 : -1)
|
||||||
setValue(next)
|
setState("value", next)
|
||||||
},
|
},
|
||||||
{ defer: true },
|
{ defer: true },
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { createEffect, createSignal, For, Match, on, onCleanup, Show, Switch, type JSX } from "solid-js"
|
import { createEffect, For, Match, on, onCleanup, Show, Switch, type JSX } from "solid-js"
|
||||||
import { animate, type AnimationPlaybackControls } from "motion"
|
import { animate, type AnimationPlaybackControls } from "motion"
|
||||||
|
import { createStore } from "solid-js/store"
|
||||||
import { Collapsible } from "./collapsible"
|
import { Collapsible } from "./collapsible"
|
||||||
import type { IconProps } from "./icon"
|
import type { IconProps } from "./icon"
|
||||||
import { TextShimmer } from "./text-shimmer"
|
import { TextShimmer } from "./text-shimmer"
|
||||||
@@ -37,8 +38,12 @@ export interface BasicToolProps {
|
|||||||
const SPRING = { type: "spring" as const, visualDuration: 0.35, bounce: 0 }
|
const SPRING = { type: "spring" as const, visualDuration: 0.35, bounce: 0 }
|
||||||
|
|
||||||
export function BasicTool(props: BasicToolProps) {
|
export function BasicTool(props: BasicToolProps) {
|
||||||
const [open, setOpen] = createSignal(props.defaultOpen ?? false)
|
const [state, setState] = createStore({
|
||||||
const [ready, setReady] = createSignal(open())
|
open: props.defaultOpen ?? false,
|
||||||
|
ready: props.defaultOpen ?? false,
|
||||||
|
})
|
||||||
|
const open = () => state.open
|
||||||
|
const ready = () => state.ready
|
||||||
const pending = () => props.status === "pending" || props.status === "running"
|
const pending = () => props.status === "pending" || props.status === "running"
|
||||||
|
|
||||||
let frame: number | undefined
|
let frame: number | undefined
|
||||||
@@ -52,7 +57,7 @@ export function BasicTool(props: BasicToolProps) {
|
|||||||
onCleanup(cancel)
|
onCleanup(cancel)
|
||||||
|
|
||||||
createEffect(() => {
|
createEffect(() => {
|
||||||
if (props.forceOpen) setOpen(true)
|
if (props.forceOpen) setState("open", true)
|
||||||
})
|
})
|
||||||
|
|
||||||
createEffect(
|
createEffect(
|
||||||
@@ -62,7 +67,7 @@ export function BasicTool(props: BasicToolProps) {
|
|||||||
if (!props.defer) return
|
if (!props.defer) return
|
||||||
if (!value) {
|
if (!value) {
|
||||||
cancel()
|
cancel()
|
||||||
setReady(false)
|
setState("ready", false)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -70,7 +75,7 @@ export function BasicTool(props: BasicToolProps) {
|
|||||||
frame = requestAnimationFrame(() => {
|
frame = requestAnimationFrame(() => {
|
||||||
frame = undefined
|
frame = undefined
|
||||||
if (!open()) return
|
if (!open()) return
|
||||||
setReady(true)
|
setState("ready", true)
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
{ defer: true },
|
{ defer: true },
|
||||||
@@ -112,7 +117,7 @@ export function BasicTool(props: BasicToolProps) {
|
|||||||
const handleOpenChange = (value: boolean) => {
|
const handleOpenChange = (value: boolean) => {
|
||||||
if (pending()) return
|
if (pending()) return
|
||||||
if (props.locked && !value) return
|
if (props.locked && !value) return
|
||||||
setOpen(value)
|
setState("open", value)
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { type DiffLineAnnotation, type SelectedLineRange } from "@pierre/diffs"
|
import { type DiffLineAnnotation, type SelectedLineRange } from "@pierre/diffs"
|
||||||
import { createEffect, createMemo, createSignal, onCleanup, Show, type Accessor, type JSX } from "solid-js"
|
import { createEffect, createMemo, createSignal, onCleanup, Show, type Accessor, type JSX } from "solid-js"
|
||||||
|
import { createStore } from "solid-js/store"
|
||||||
import { render as renderSolid } from "solid-js/web"
|
import { render as renderSolid } from "solid-js/web"
|
||||||
import { createHoverCommentUtility } from "../pierre/comment-hover"
|
import { createHoverCommentUtility } from "../pierre/comment-hover"
|
||||||
import { cloneSelectedLineRange, formatSelectedLineLabel, lineInSelectedRange } from "../pierre/selection-bridge"
|
import { cloneSelectedLineRange, formatSelectedLineLabel, lineInSelectedRange } from "../pierre/selection-bridge"
|
||||||
@@ -200,8 +201,14 @@ export function createLineCommentAnnotationRenderer<T>(props: {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function createLineCommentState<T>(props: LineCommentStateProps<T>) {
|
export function createLineCommentState<T>(props: LineCommentStateProps<T>) {
|
||||||
const [draft, setDraft] = createSignal("")
|
const [state, setState] = createStore({
|
||||||
const [editing, setEditing] = createSignal<T | null>(null)
|
draft: "",
|
||||||
|
editing: null as T | null,
|
||||||
|
})
|
||||||
|
const draft = () => state.draft
|
||||||
|
const setDraft = (value: string) => setState("draft", value)
|
||||||
|
const editing = () => state.editing
|
||||||
|
const setEditing = (value: T | null) => setState("editing", typeof value === "function" ? () => value : value)
|
||||||
|
|
||||||
const toRange = (range: SelectedLineRange | null) => (range ? cloneSelectedLineRange(range) : null)
|
const toRange = (range: SelectedLineRange | null) => (range ? cloneSelectedLineRange(range) : null)
|
||||||
const setSelected = (range: SelectedLineRange | null) => {
|
const setSelected = (range: SelectedLineRange | null) => {
|
||||||
@@ -261,7 +268,7 @@ export function createLineCommentState<T>(props: LineCommentStateProps<T>) {
|
|||||||
closeComment()
|
closeComment()
|
||||||
setSelected(range)
|
setSelected(range)
|
||||||
props.setCommenting(null)
|
props.setCommenting(null)
|
||||||
setEditing(() => id)
|
setEditing(id)
|
||||||
setDraft(value)
|
setDraft(value)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { type FilteredListProps, useFilteredList } from "@opencode-ai/ui/hooks"
|
import { type FilteredListProps, useFilteredList } from "@opencode-ai/ui/hooks"
|
||||||
import { createEffect, createSignal, For, onCleanup, type JSX, on, Show } from "solid-js"
|
import { createEffect, For, onCleanup, type JSX, on, Show } from "solid-js"
|
||||||
import { createStore } from "solid-js/store"
|
import { createStore } from "solid-js/store"
|
||||||
import { useI18n } from "../context/i18n"
|
import { useI18n } from "../context/i18n"
|
||||||
import { Icon, type IconProps } from "./icon"
|
import { Icon, type IconProps } from "./icon"
|
||||||
@@ -56,12 +56,16 @@ export interface ListRef {
|
|||||||
|
|
||||||
export function List<T>(props: ListProps<T> & { ref?: (ref: ListRef) => void }) {
|
export function List<T>(props: ListProps<T> & { ref?: (ref: ListRef) => void }) {
|
||||||
const i18n = useI18n()
|
const i18n = useI18n()
|
||||||
const [scrollRef, setScrollRef] = createSignal<HTMLDivElement | undefined>(undefined)
|
|
||||||
const [internalFilter, setInternalFilter] = createSignal("")
|
|
||||||
let inputRef: HTMLInputElement | HTMLTextAreaElement | undefined
|
let inputRef: HTMLInputElement | HTMLTextAreaElement | undefined
|
||||||
const [store, setStore] = createStore({
|
const [store, setStore] = createStore({
|
||||||
mouseActive: false,
|
mouseActive: false,
|
||||||
|
scrollRef: undefined as HTMLDivElement | undefined,
|
||||||
|
internalFilter: "",
|
||||||
})
|
})
|
||||||
|
const scrollRef = () => store.scrollRef
|
||||||
|
const setScrollRef = (el: HTMLDivElement | undefined) => setStore("scrollRef", el)
|
||||||
|
const internalFilter = () => store.internalFilter
|
||||||
|
const setInternalFilter = (value: string) => setStore("internalFilter", value)
|
||||||
|
|
||||||
const scrollIntoView = (container: HTMLDivElement, node: HTMLElement, block: "center" | "nearest") => {
|
const scrollIntoView = (container: HTMLDivElement, node: HTMLElement, block: "center" | "nearest") => {
|
||||||
const containerRect = container.getBoundingClientRect()
|
const containerRect = container.getBoundingClientRect()
|
||||||
@@ -208,18 +212,20 @@ export function List<T>(props: ListProps<T> & { ref?: (ref: ListRef) => void })
|
|||||||
}
|
}
|
||||||
|
|
||||||
function GroupHeader(groupProps: { group: { category: string; items: T[] } }): JSX.Element {
|
function GroupHeader(groupProps: { group: { category: string; items: T[] } }): JSX.Element {
|
||||||
const [stuck, setStuck] = createSignal(false)
|
const [state, setState] = createStore({
|
||||||
const [header, setHeader] = createSignal<HTMLDivElement | undefined>(undefined)
|
stuck: false,
|
||||||
|
header: undefined as HTMLDivElement | undefined,
|
||||||
|
})
|
||||||
|
|
||||||
createEffect(() => {
|
createEffect(() => {
|
||||||
const scroll = scrollRef()
|
const scroll = scrollRef()
|
||||||
const node = header()
|
const node = state.header
|
||||||
if (!scroll || !node) return
|
if (!scroll || !node) return
|
||||||
|
|
||||||
const handler = () => {
|
const handler = () => {
|
||||||
const rect = node.getBoundingClientRect()
|
const rect = node.getBoundingClientRect()
|
||||||
const scrollRect = scroll.getBoundingClientRect()
|
const scrollRect = scroll.getBoundingClientRect()
|
||||||
setStuck(rect.top <= scrollRect.top + 1 && scroll.scrollTop > 0)
|
setState("stuck", rect.top <= scrollRect.top + 1 && scroll.scrollTop > 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
scroll.addEventListener("scroll", handler, { passive: true })
|
scroll.addEventListener("scroll", handler, { passive: true })
|
||||||
@@ -228,7 +234,7 @@ export function List<T>(props: ListProps<T> & { ref?: (ref: ListRef) => void })
|
|||||||
})
|
})
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div data-slot="list-header" data-stuck={stuck()} ref={setHeader}>
|
<div data-slot="list-header" data-stuck={state.stuck} ref={(el) => setState("header", el)}>
|
||||||
{props.groupHeader?.(groupProps.group) ?? groupProps.group.category}
|
{props.groupHeader?.(groupProps.group) ?? groupProps.group.category}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import {
|
|||||||
Index,
|
Index,
|
||||||
type JSX,
|
type JSX,
|
||||||
} from "solid-js"
|
} from "solid-js"
|
||||||
|
import { createStore } from "solid-js/store"
|
||||||
import stripAnsi from "strip-ansi"
|
import stripAnsi from "strip-ansi"
|
||||||
import { Dynamic } from "solid-js/web"
|
import { Dynamic } from "solid-js/web"
|
||||||
import {
|
import {
|
||||||
@@ -885,8 +886,12 @@ export function UserMessageDisplay(props: { message: UserMessage; parts: PartTyp
|
|||||||
const data = useData()
|
const data = useData()
|
||||||
const dialog = useDialog()
|
const dialog = useDialog()
|
||||||
const i18n = useI18n()
|
const i18n = useI18n()
|
||||||
const [copied, setCopied] = createSignal(false)
|
const [state, setState] = createStore({
|
||||||
const [busy, setBusy] = createSignal<"fork" | "revert" | undefined>()
|
copied: false,
|
||||||
|
busy: undefined as "fork" | "revert" | undefined,
|
||||||
|
})
|
||||||
|
const copied = () => state.copied
|
||||||
|
const busy = () => state.busy
|
||||||
|
|
||||||
const textPart = createMemo(
|
const textPart = createMemo(
|
||||||
() => props.parts?.find((p) => p.type === "text" && !(p as TextPart).synthetic) as TextPart | undefined,
|
() => props.parts?.find((p) => p.type === "text" && !(p as TextPart).synthetic) as TextPart | undefined,
|
||||||
@@ -946,14 +951,14 @@ export function UserMessageDisplay(props: { message: UserMessage; parts: PartTyp
|
|||||||
const content = text()
|
const content = text()
|
||||||
if (!content) return
|
if (!content) return
|
||||||
await navigator.clipboard.writeText(content)
|
await navigator.clipboard.writeText(content)
|
||||||
setCopied(true)
|
setState("copied", true)
|
||||||
setTimeout(() => setCopied(false), 2000)
|
setTimeout(() => setState("copied", false), 2000)
|
||||||
}
|
}
|
||||||
|
|
||||||
const run = (kind: "fork" | "revert") => {
|
const run = (kind: "fork" | "revert") => {
|
||||||
const act = kind === "fork" ? props.actions?.fork : props.actions?.revert
|
const act = kind === "fork" ? props.actions?.fork : props.actions?.revert
|
||||||
if (!act || busy()) return
|
if (!act || busy()) return
|
||||||
setBusy(kind)
|
setState("busy", kind)
|
||||||
void Promise.resolve()
|
void Promise.resolve()
|
||||||
.then(() =>
|
.then(() =>
|
||||||
act({
|
act({
|
||||||
@@ -962,7 +967,7 @@ export function UserMessageDisplay(props: { message: UserMessage; parts: PartTyp
|
|||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
.finally(() => {
|
.finally(() => {
|
||||||
if (busy() === kind) setBusy(undefined)
|
if (busy() === kind) setState("busy", undefined)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,11 +5,11 @@ import {
|
|||||||
ParentProps,
|
ParentProps,
|
||||||
Show,
|
Show,
|
||||||
createEffect,
|
createEffect,
|
||||||
createSignal,
|
|
||||||
onCleanup,
|
onCleanup,
|
||||||
splitProps,
|
splitProps,
|
||||||
ValidComponent,
|
ValidComponent,
|
||||||
} from "solid-js"
|
} from "solid-js"
|
||||||
|
import { createStore } from "solid-js/store"
|
||||||
import { useI18n } from "../context/i18n"
|
import { useI18n } from "../context/i18n"
|
||||||
import { IconButton } from "./icon-button"
|
import { IconButton } from "./icon-button"
|
||||||
|
|
||||||
@@ -46,23 +46,24 @@ export function Popover<T extends ValidComponent = "div">(props: PopoverProps<T>
|
|||||||
"modal",
|
"modal",
|
||||||
])
|
])
|
||||||
|
|
||||||
const [contentRef, setContentRef] = createSignal<HTMLElement | undefined>(undefined)
|
const [state, setState] = createStore({
|
||||||
const [triggerRef, setTriggerRef] = createSignal<HTMLElement | undefined>(undefined)
|
contentRef: undefined as HTMLElement | undefined,
|
||||||
const [dismiss, setDismiss] = createSignal<"escape" | "outside" | null>(null)
|
triggerRef: undefined as HTMLElement | undefined,
|
||||||
|
dismiss: null as "escape" | "outside" | null,
|
||||||
const [uncontrolledOpen, setUncontrolledOpen] = createSignal<boolean>(local.defaultOpen ?? false)
|
uncontrolledOpen: local.defaultOpen ?? false,
|
||||||
|
})
|
||||||
|
|
||||||
const controlled = () => local.open !== undefined
|
const controlled = () => local.open !== undefined
|
||||||
const opened = () => {
|
const opened = () => {
|
||||||
if (controlled()) return local.open ?? false
|
if (controlled()) return local.open ?? false
|
||||||
return uncontrolledOpen()
|
return state.uncontrolledOpen
|
||||||
}
|
}
|
||||||
|
|
||||||
const onOpenChange = (next: boolean) => {
|
const onOpenChange = (next: boolean) => {
|
||||||
if (next) setDismiss(null)
|
if (next) setState("dismiss", null)
|
||||||
if (local.onOpenChange) local.onOpenChange(next)
|
if (local.onOpenChange) local.onOpenChange(next)
|
||||||
if (controlled()) return
|
if (controlled()) return
|
||||||
setUncontrolledOpen(next)
|
setState("uncontrolledOpen", next)
|
||||||
}
|
}
|
||||||
|
|
||||||
createEffect(() => {
|
createEffect(() => {
|
||||||
@@ -70,15 +71,15 @@ export function Popover<T extends ValidComponent = "div">(props: PopoverProps<T>
|
|||||||
|
|
||||||
const inside = (node: Node | null | undefined) => {
|
const inside = (node: Node | null | undefined) => {
|
||||||
if (!node) return false
|
if (!node) return false
|
||||||
const content = contentRef()
|
const content = state.contentRef
|
||||||
if (content && content.contains(node)) return true
|
if (content && content.contains(node)) return true
|
||||||
const trigger = triggerRef()
|
const trigger = state.triggerRef
|
||||||
if (trigger && trigger.contains(node)) return true
|
if (trigger && trigger.contains(node)) return true
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
const close = (reason: "escape" | "outside") => {
|
const close = (reason: "escape" | "outside") => {
|
||||||
setDismiss(reason)
|
setState("dismiss", reason)
|
||||||
onOpenChange(false)
|
onOpenChange(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -116,7 +117,7 @@ export function Popover<T extends ValidComponent = "div">(props: PopoverProps<T>
|
|||||||
|
|
||||||
const content = () => (
|
const content = () => (
|
||||||
<Kobalte.Content
|
<Kobalte.Content
|
||||||
ref={(el: HTMLElement | undefined) => setContentRef(el)}
|
ref={(el: HTMLElement | undefined) => setState("contentRef", el)}
|
||||||
data-component="popover-content"
|
data-component="popover-content"
|
||||||
classList={{
|
classList={{
|
||||||
...(local.classList ?? {}),
|
...(local.classList ?? {}),
|
||||||
@@ -124,8 +125,8 @@ export function Popover<T extends ValidComponent = "div">(props: PopoverProps<T>
|
|||||||
}}
|
}}
|
||||||
style={local.style}
|
style={local.style}
|
||||||
onCloseAutoFocus={(event: Event) => {
|
onCloseAutoFocus={(event: Event) => {
|
||||||
if (dismiss() === "outside") event.preventDefault()
|
if (state.dismiss === "outside") event.preventDefault()
|
||||||
setDismiss(null)
|
setState("dismiss", null)
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{/* <Kobalte.Arrow data-slot="popover-arrow" /> */}
|
{/* <Kobalte.Arrow data-slot="popover-arrow" /> */}
|
||||||
@@ -151,7 +152,7 @@ export function Popover<T extends ValidComponent = "div">(props: PopoverProps<T>
|
|||||||
return (
|
return (
|
||||||
<Kobalte gutter={4} {...rest} open={opened()} onOpenChange={onOpenChange} modal={local.modal ?? false}>
|
<Kobalte gutter={4} {...rest} open={opened()} onOpenChange={onOpenChange} modal={local.modal ?? false}>
|
||||||
<Kobalte.Trigger
|
<Kobalte.Trigger
|
||||||
ref={(el: HTMLElement) => setTriggerRef(el)}
|
ref={(el: HTMLElement) => setState("triggerRef", el)}
|
||||||
as={local.triggerAs ?? "div"}
|
as={local.triggerAs ?? "div"}
|
||||||
data-slot="popover-trigger"
|
data-slot="popover-trigger"
|
||||||
{...(local.triggerProps as any)}
|
{...(local.triggerProps as any)}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
// @ts-nocheck
|
// @ts-nocheck
|
||||||
import { createSignal } from "solid-js"
|
import { createSignal } from "solid-js"
|
||||||
|
import { createStore } from "solid-js/store"
|
||||||
import * as mod from "./resize-handle"
|
import * as mod from "./resize-handle"
|
||||||
|
|
||||||
const docs = `### Overview
|
const docs = `### Overview
|
||||||
@@ -94,8 +95,12 @@ export const Vertical = {
|
|||||||
|
|
||||||
export const Collapse = {
|
export const Collapse = {
|
||||||
render: () => {
|
render: () => {
|
||||||
const [size, setSize] = createSignal(200)
|
const [state, setState] = createStore({
|
||||||
const [collapsed, setCollapsed] = createSignal(false)
|
size: 200,
|
||||||
|
collapsed: false,
|
||||||
|
})
|
||||||
|
const size = () => state.size
|
||||||
|
const collapsed = () => state.collapsed
|
||||||
return (
|
return (
|
||||||
<div style={{ display: "grid", gap: "8px" }}>
|
<div style={{ display: "grid", gap: "8px" }}>
|
||||||
<div style={{ color: "var(--text-weak)", "font-size": "12px" }}>
|
<div style={{ color: "var(--text-weak)", "font-size": "12px" }}>
|
||||||
@@ -116,10 +121,10 @@ export const Collapse = {
|
|||||||
max={360}
|
max={360}
|
||||||
collapseThreshold={100}
|
collapseThreshold={100}
|
||||||
onResize={(next) => {
|
onResize={(next) => {
|
||||||
setCollapsed(false)
|
setState("collapsed", false)
|
||||||
setSize(next)
|
setState("size", next)
|
||||||
}}
|
}}
|
||||||
onCollapse={() => setCollapsed(true)}
|
onCollapse={() => setState("collapsed", true)}
|
||||||
style="height:24px;border:1px dashed color-mix(in oklab, var(--text-base) 20%, transparent)"
|
style="height:24px;border:1px dashed color-mix(in oklab, var(--text-base) 20%, transparent)"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { createSignal, onCleanup, onMount, splitProps, type ComponentProps, Show, mergeProps } from "solid-js"
|
import { onCleanup, onMount, splitProps, type ComponentProps, Show, mergeProps } from "solid-js"
|
||||||
|
import { createStore } from "solid-js/store"
|
||||||
import { useI18n } from "../context/i18n"
|
import { useI18n } from "../context/i18n"
|
||||||
|
|
||||||
export interface ScrollViewProps extends ComponentProps<"div"> {
|
export interface ScrollViewProps extends ComponentProps<"div"> {
|
||||||
@@ -48,23 +49,29 @@ export function ScrollView(props: ScrollViewProps) {
|
|||||||
let viewportRef!: HTMLDivElement
|
let viewportRef!: HTMLDivElement
|
||||||
let thumbRef!: HTMLDivElement
|
let thumbRef!: HTMLDivElement
|
||||||
|
|
||||||
const [isHovered, setIsHovered] = createSignal(false)
|
const [state, setState] = createStore({
|
||||||
const [isDragging, setIsDragging] = createSignal(false)
|
isHovered: false,
|
||||||
|
isDragging: false,
|
||||||
const [thumbHeight, setThumbHeight] = createSignal(0)
|
thumbHeight: 0,
|
||||||
const [thumbTop, setThumbTop] = createSignal(0)
|
thumbTop: 0,
|
||||||
const [showThumb, setShowThumb] = createSignal(false)
|
showThumb: false,
|
||||||
|
})
|
||||||
|
const isHovered = () => state.isHovered
|
||||||
|
const isDragging = () => state.isDragging
|
||||||
|
const thumbHeight = () => state.thumbHeight
|
||||||
|
const thumbTop = () => state.thumbTop
|
||||||
|
const showThumb = () => state.showThumb
|
||||||
|
|
||||||
const updateThumb = () => {
|
const updateThumb = () => {
|
||||||
if (!viewportRef) return
|
if (!viewportRef) return
|
||||||
const { scrollTop, scrollHeight, clientHeight } = viewportRef
|
const { scrollTop, scrollHeight, clientHeight } = viewportRef
|
||||||
|
|
||||||
if (scrollHeight <= clientHeight || scrollHeight === 0) {
|
if (scrollHeight <= clientHeight || scrollHeight === 0) {
|
||||||
setShowThumb(false)
|
setState("showThumb", false)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
setShowThumb(true)
|
setState("showThumb", true)
|
||||||
const trackPadding = 8
|
const trackPadding = 8
|
||||||
const trackHeight = clientHeight - trackPadding * 2
|
const trackHeight = clientHeight - trackPadding * 2
|
||||||
|
|
||||||
@@ -81,8 +88,8 @@ export function ScrollView(props: ScrollViewProps) {
|
|||||||
// Ensure thumb stays within bounds (shouldn't be necessary due to math above, but good for safety)
|
// Ensure thumb stays within bounds (shouldn't be necessary due to math above, but good for safety)
|
||||||
const boundedTop = trackPadding + Math.max(0, Math.min(top, maxThumbTop))
|
const boundedTop = trackPadding + Math.max(0, Math.min(top, maxThumbTop))
|
||||||
|
|
||||||
setThumbHeight(height)
|
setState("thumbHeight", height)
|
||||||
setThumbTop(boundedTop)
|
setState("thumbTop", boundedTop)
|
||||||
}
|
}
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
@@ -113,7 +120,7 @@ export function ScrollView(props: ScrollViewProps) {
|
|||||||
const onThumbPointerDown = (e: PointerEvent) => {
|
const onThumbPointerDown = (e: PointerEvent) => {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
e.stopPropagation()
|
e.stopPropagation()
|
||||||
setIsDragging(true)
|
setState("isDragging", true)
|
||||||
startY = e.clientY
|
startY = e.clientY
|
||||||
startScrollTop = viewportRef.scrollTop
|
startScrollTop = viewportRef.scrollTop
|
||||||
|
|
||||||
@@ -132,7 +139,7 @@ export function ScrollView(props: ScrollViewProps) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const onPointerUp = (e: PointerEvent) => {
|
const onPointerUp = (e: PointerEvent) => {
|
||||||
setIsDragging(false)
|
setState("isDragging", false)
|
||||||
thumbRef.releasePointerCapture(e.pointerId)
|
thumbRef.releasePointerCapture(e.pointerId)
|
||||||
thumbRef.removeEventListener("pointermove", onPointerMove)
|
thumbRef.removeEventListener("pointermove", onPointerMove)
|
||||||
thumbRef.removeEventListener("pointerup", onPointerUp)
|
thumbRef.removeEventListener("pointerup", onPointerUp)
|
||||||
@@ -191,8 +198,8 @@ export function ScrollView(props: ScrollViewProps) {
|
|||||||
ref={rootRef}
|
ref={rootRef}
|
||||||
class={`scroll-view ${local.class || ""}`}
|
class={`scroll-view ${local.class || ""}`}
|
||||||
style={local.style}
|
style={local.style}
|
||||||
onPointerEnter={() => setIsHovered(true)}
|
onPointerEnter={() => setState("isHovered", true)}
|
||||||
onPointerLeave={() => setIsHovered(false)}
|
onPointerLeave={() => setState("isHovered", false)}
|
||||||
{...rest}
|
{...rest}
|
||||||
>
|
>
|
||||||
{/* Viewport */}
|
{/* Viewport */}
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import { useFileComponent } from "../context/file"
|
|||||||
import { useI18n } from "../context/i18n"
|
import { useI18n } from "../context/i18n"
|
||||||
import { getDirectory, getFilename } from "@opencode-ai/util/path"
|
import { getDirectory, getFilename } from "@opencode-ai/util/path"
|
||||||
import { checksum } from "@opencode-ai/util/encode"
|
import { checksum } from "@opencode-ai/util/encode"
|
||||||
import { createEffect, createMemo, createSignal, For, Match, Show, Switch, untrack, type JSX } from "solid-js"
|
import { createEffect, createMemo, For, Match, Show, Switch, untrack, type JSX } from "solid-js"
|
||||||
import { onCleanup } from "solid-js"
|
import { onCleanup } from "solid-js"
|
||||||
import { createStore } from "solid-js/store"
|
import { createStore } from "solid-js/store"
|
||||||
import { type FileContent, type FileDiff } from "@opencode-ai/sdk/v2"
|
import { type FileContent, type FileDiff } from "@opencode-ai/sdk/v2"
|
||||||
@@ -138,14 +138,16 @@ export const SessionReview = (props: SessionReviewProps) => {
|
|||||||
const i18n = useI18n()
|
const i18n = useI18n()
|
||||||
const fileComponent = useFileComponent()
|
const fileComponent = useFileComponent()
|
||||||
const anchors = new Map<string, HTMLElement>()
|
const anchors = new Map<string, HTMLElement>()
|
||||||
const [store, setStore] = createStore<{ open: string[]; force: Record<string, boolean> }>({
|
const [store, setStore] = createStore({
|
||||||
open: [],
|
open: [] as string[],
|
||||||
force: {},
|
force: {} as Record<string, boolean>,
|
||||||
|
selection: null as SessionReviewSelection | null,
|
||||||
|
commenting: null as SessionReviewSelection | null,
|
||||||
|
opened: null as SessionReviewFocus | null,
|
||||||
})
|
})
|
||||||
|
const selection = () => store.selection
|
||||||
const [selection, setSelection] = createSignal<SessionReviewSelection | null>(null)
|
const commenting = () => store.commenting
|
||||||
const [commenting, setCommenting] = createSignal<SessionReviewSelection | null>(null)
|
const opened = () => store.opened
|
||||||
const [opened, setOpened] = createSignal<SessionReviewFocus | null>(null)
|
|
||||||
|
|
||||||
const open = () => props.open ?? store.open
|
const open = () => props.open ?? store.open
|
||||||
const files = createMemo(() => props.diffs.map((diff) => diff.file))
|
const files = createMemo(() => props.diffs.map((diff) => diff.file))
|
||||||
@@ -184,10 +186,10 @@ export const SessionReview = (props: SessionReviewProps) => {
|
|||||||
focusToken++
|
focusToken++
|
||||||
const token = focusToken
|
const token = focusToken
|
||||||
|
|
||||||
setOpened(focus)
|
setStore("opened", focus)
|
||||||
|
|
||||||
const comment = (props.comments ?? []).find((c) => c.file === focus.file && c.id === focus.id)
|
const comment = (props.comments ?? []).find((c) => c.file === focus.file && c.id === focus.id)
|
||||||
if (comment) setSelection({ file: comment.file, range: cloneSelectedLineRange(comment.selection) })
|
if (comment) setStore("selection", { file: comment.file, range: cloneSelectedLineRange(comment.selection) })
|
||||||
|
|
||||||
const current = open()
|
const current = open()
|
||||||
if (!current.includes(focus.file)) {
|
if (!current.includes(focus.file)) {
|
||||||
@@ -331,11 +333,11 @@ export const SessionReview = (props: SessionReviewProps) => {
|
|||||||
if (!current || current.file !== file) return null
|
if (!current || current.file !== file) return null
|
||||||
return current.id
|
return current.id
|
||||||
},
|
},
|
||||||
setOpened: (id) => setOpened(id ? { file, id } : null),
|
setOpened: (id) => setStore("opened", id ? { file, id } : null),
|
||||||
selected: selectedLines,
|
selected: selectedLines,
|
||||||
setSelected: (range) => setSelection(range ? { file, range } : null),
|
setSelected: (range) => setStore("selection", range ? { file, range } : null),
|
||||||
commenting: draftRange,
|
commenting: draftRange,
|
||||||
setCommenting: (range) => setCommenting(range ? { file, range } : null),
|
setCommenting: (range) => setStore("commenting", range ? { file, range } : null),
|
||||||
},
|
},
|
||||||
getSide: selectionSide,
|
getSide: selectionSide,
|
||||||
clearSelectionOnSelectionEndNull: false,
|
clearSelectionOnSelectionEndNull: false,
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import { useFileComponent } from "../context/file"
|
|||||||
import { Binary } from "@opencode-ai/util/binary"
|
import { Binary } from "@opencode-ai/util/binary"
|
||||||
import { getDirectory, getFilename } from "@opencode-ai/util/path"
|
import { getDirectory, getFilename } from "@opencode-ai/util/path"
|
||||||
import { createEffect, createMemo, createSignal, For, on, ParentProps, Show } from "solid-js"
|
import { createEffect, createMemo, createSignal, For, on, ParentProps, Show } from "solid-js"
|
||||||
|
import { createStore } from "solid-js/store"
|
||||||
import { Dynamic } from "solid-js/web"
|
import { Dynamic } from "solid-js/web"
|
||||||
import { AssistantParts, Message, MessageDivider, PART_MAPPING, type UserActions } from "./message-part"
|
import { AssistantParts, Message, MessageDivider, PART_MAPPING, type UserActions } from "./message-part"
|
||||||
import { Card } from "./card"
|
import { Card } from "./card"
|
||||||
@@ -240,14 +241,18 @@ export function SessionTurn(
|
|||||||
.reverse()
|
.reverse()
|
||||||
})
|
})
|
||||||
const edited = createMemo(() => diffs().length)
|
const edited = createMemo(() => diffs().length)
|
||||||
const [open, setOpen] = createSignal(false)
|
const [state, setState] = createStore({
|
||||||
const [expanded, setExpanded] = createSignal<string[]>([])
|
open: false,
|
||||||
|
expanded: [] as string[],
|
||||||
|
})
|
||||||
|
const open = () => state.open
|
||||||
|
const expanded = () => state.expanded
|
||||||
|
|
||||||
createEffect(
|
createEffect(
|
||||||
on(
|
on(
|
||||||
open,
|
open,
|
||||||
(value, prev) => {
|
(value, prev) => {
|
||||||
if (!value && prev) setExpanded([])
|
if (!value && prev) setState("expanded", [])
|
||||||
},
|
},
|
||||||
{ defer: true },
|
{ defer: true },
|
||||||
),
|
),
|
||||||
@@ -425,7 +430,7 @@ export function SessionTurn(
|
|||||||
<SessionRetry status={status()} show={active()} />
|
<SessionRetry status={status()} show={active()} />
|
||||||
<Show when={edited() > 0 && !working()}>
|
<Show when={edited() > 0 && !working()}>
|
||||||
<div data-slot="session-turn-diffs">
|
<div data-slot="session-turn-diffs">
|
||||||
<Collapsible open={open()} onOpenChange={setOpen} variant="ghost">
|
<Collapsible open={open()} onOpenChange={(value) => setState("open", value)} variant="ghost">
|
||||||
<Collapsible.Trigger>
|
<Collapsible.Trigger>
|
||||||
<div data-component="session-turn-diffs-trigger">
|
<div data-component="session-turn-diffs-trigger">
|
||||||
<div data-slot="session-turn-diffs-title">
|
<div data-slot="session-turn-diffs-title">
|
||||||
@@ -447,7 +452,9 @@ export function SessionTurn(
|
|||||||
multiple
|
multiple
|
||||||
style={{ "--sticky-accordion-offset": "40px" }}
|
style={{ "--sticky-accordion-offset": "40px" }}
|
||||||
value={expanded()}
|
value={expanded()}
|
||||||
onChange={(value) => setExpanded(Array.isArray(value) ? value : value ? [value] : [])}
|
onChange={(value) =>
|
||||||
|
setState("expanded", Array.isArray(value) ? value : value ? [value] : [])
|
||||||
|
}
|
||||||
>
|
>
|
||||||
<For each={diffs()}>
|
<For each={diffs()}>
|
||||||
{(diff) => {
|
{(diff) => {
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
// @ts-nocheck
|
// @ts-nocheck
|
||||||
import { createEffect, createSignal, onCleanup } from "solid-js"
|
import { createEffect, onCleanup } from "solid-js"
|
||||||
|
import { createStore } from "solid-js/store"
|
||||||
import { BasicTool } from "./basic-tool"
|
import { BasicTool } from "./basic-tool"
|
||||||
import { animate } from "motion"
|
import { animate } from "motion"
|
||||||
|
|
||||||
@@ -138,29 +139,39 @@ function SpringSubmessage(props: { text: string; visible: boolean; visualDuratio
|
|||||||
|
|
||||||
export const Playground = {
|
export const Playground = {
|
||||||
render: () => {
|
render: () => {
|
||||||
const [text, setText] = createSignal("Prints five topic blocks between timed commands")
|
const [state, setState] = createStore({
|
||||||
const [show, setShow] = createSignal(true)
|
text: "Prints five topic blocks between timed commands",
|
||||||
const [visualDuration, setVisualDuration] = createSignal(0.35)
|
show: true,
|
||||||
const [bounce, setBounce] = createSignal(0)
|
visualDuration: 0.35,
|
||||||
const [fadeMs, setFadeMs] = createSignal(320)
|
bounce: 0,
|
||||||
const [blur, setBlur] = createSignal(2)
|
fadeMs: 320,
|
||||||
const [fadeEase, setFadeEase] = createSignal<keyof typeof ease>("snappy")
|
blur: 2,
|
||||||
const [auto, setAuto] = createSignal(false)
|
fadeEase: "snappy",
|
||||||
|
auto: false,
|
||||||
|
})
|
||||||
|
const text = () => state.text
|
||||||
|
const show = () => state.show
|
||||||
|
const visualDuration = () => state.visualDuration
|
||||||
|
const bounce = () => state.bounce
|
||||||
|
const fadeMs = () => state.fadeMs
|
||||||
|
const blur = () => state.blur
|
||||||
|
const fadeEase = () => state.fadeEase
|
||||||
|
const auto = () => state.auto
|
||||||
let replayTimer
|
let replayTimer
|
||||||
let autoTimer
|
let autoTimer
|
||||||
|
|
||||||
const replay = () => {
|
const replay = () => {
|
||||||
setShow(false)
|
setState("show", false)
|
||||||
if (replayTimer) clearTimeout(replayTimer)
|
if (replayTimer) clearTimeout(replayTimer)
|
||||||
replayTimer = setTimeout(() => {
|
replayTimer = setTimeout(() => {
|
||||||
setShow(true)
|
setState("show", true)
|
||||||
}, 50)
|
}, 50)
|
||||||
}
|
}
|
||||||
|
|
||||||
const stopAuto = () => {
|
const stopAuto = () => {
|
||||||
if (autoTimer) clearInterval(autoTimer)
|
if (autoTimer) clearInterval(autoTimer)
|
||||||
autoTimer = undefined
|
autoTimer = undefined
|
||||||
setAuto(false)
|
setState("auto", false)
|
||||||
}
|
}
|
||||||
|
|
||||||
const toggleAuto = () => {
|
const toggleAuto = () => {
|
||||||
@@ -168,7 +179,7 @@ export const Playground = {
|
|||||||
stopAuto()
|
stopAuto()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
setAuto(true)
|
setState("auto", true)
|
||||||
autoTimer = setInterval(replay, 2200)
|
autoTimer = setInterval(replay, 2200)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -224,7 +235,7 @@ export const Playground = {
|
|||||||
<button onClick={replay} style={btn()}>
|
<button onClick={replay} style={btn()}>
|
||||||
Replay entry
|
Replay entry
|
||||||
</button>
|
</button>
|
||||||
<button onClick={() => setShow((v) => !v)} style={btn(show())}>
|
<button onClick={() => setState("show", (value) => !value)} style={btn(show())}>
|
||||||
{show() ? "Hide subtitle" : "Show subtitle"}
|
{show() ? "Hide subtitle" : "Show subtitle"}
|
||||||
</button>
|
</button>
|
||||||
<button onClick={toggleAuto} style={btn(auto())}>
|
<button onClick={toggleAuto} style={btn(auto())}>
|
||||||
@@ -244,7 +255,7 @@ export const Playground = {
|
|||||||
<span style={sliderLabel}>subtitle</span>
|
<span style={sliderLabel}>subtitle</span>
|
||||||
<input
|
<input
|
||||||
value={text()}
|
value={text()}
|
||||||
onInput={(e) => setText(e.currentTarget.value)}
|
onInput={(e) => setState("text", e.currentTarget.value)}
|
||||||
style={{
|
style={{
|
||||||
width: "420px",
|
width: "420px",
|
||||||
"max-width": "100%",
|
"max-width": "100%",
|
||||||
@@ -265,7 +276,7 @@ export const Playground = {
|
|||||||
max={1.5}
|
max={1.5}
|
||||||
step={0.01}
|
step={0.01}
|
||||||
value={visualDuration()}
|
value={visualDuration()}
|
||||||
onInput={(e) => setVisualDuration(Number(e.currentTarget.value))}
|
onInput={(e) => setState("visualDuration", Number(e.currentTarget.value))}
|
||||||
/>
|
/>
|
||||||
<span style={sliderValue}>{visualDuration().toFixed(2)}s</span>
|
<span style={sliderValue}>{visualDuration().toFixed(2)}s</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -278,7 +289,7 @@ export const Playground = {
|
|||||||
max={0.5}
|
max={0.5}
|
||||||
step={0.01}
|
step={0.01}
|
||||||
value={bounce()}
|
value={bounce()}
|
||||||
onInput={(e) => setBounce(Number(e.currentTarget.value))}
|
onInput={(e) => setState("bounce", Number(e.currentTarget.value))}
|
||||||
/>
|
/>
|
||||||
<span style={sliderValue}>{bounce().toFixed(2)}</span>
|
<span style={sliderValue}>{bounce().toFixed(2)}</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -287,8 +298,14 @@ export const Playground = {
|
|||||||
<span style={sliderLabel}>fade ease</span>
|
<span style={sliderLabel}>fade ease</span>
|
||||||
<button
|
<button
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
setFadeEase((v) =>
|
setState("fadeEase", (value) =>
|
||||||
v === "snappy" ? "smooth" : v === "smooth" ? "standard" : v === "standard" ? "linear" : "snappy",
|
value === "snappy"
|
||||||
|
? "smooth"
|
||||||
|
: value === "smooth"
|
||||||
|
? "standard"
|
||||||
|
: value === "standard"
|
||||||
|
? "linear"
|
||||||
|
: "snappy",
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
style={btn()}
|
style={btn()}
|
||||||
@@ -305,7 +322,7 @@ export const Playground = {
|
|||||||
max={1400}
|
max={1400}
|
||||||
step={10}
|
step={10}
|
||||||
value={fadeMs()}
|
value={fadeMs()}
|
||||||
onInput={(e) => setFadeMs(Number(e.currentTarget.value))}
|
onInput={(e) => setState("fadeMs", Number(e.currentTarget.value))}
|
||||||
/>
|
/>
|
||||||
<span style={sliderValue}>{fadeMs()}ms</span>
|
<span style={sliderValue}>{fadeMs()}ms</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -318,7 +335,7 @@ export const Playground = {
|
|||||||
max={14}
|
max={14}
|
||||||
step={0.5}
|
step={0.5}
|
||||||
value={blur()}
|
value={blur()}
|
||||||
onInput={(e) => setBlur(Number(e.currentTarget.value))}
|
onInput={(e) => setState("blur", Number(e.currentTarget.value))}
|
||||||
/>
|
/>
|
||||||
<span style={sliderValue}>{blur()}px</span>
|
<span style={sliderValue}>{blur()}px</span>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
// @ts-nocheck
|
// @ts-nocheck
|
||||||
import { createSignal, onCleanup } from "solid-js"
|
import { onCleanup } from "solid-js"
|
||||||
|
import { createStore } from "solid-js/store"
|
||||||
import { TextReveal } from "./text-reveal"
|
import { TextReveal } from "./text-reveal"
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
@@ -87,33 +88,42 @@ const headingSlot = {
|
|||||||
|
|
||||||
export const Playground = {
|
export const Playground = {
|
||||||
render: () => {
|
render: () => {
|
||||||
const [index, setIndex] = createSignal(0)
|
const [state, setState] = createStore({
|
||||||
const [cycling, setCycling] = createSignal(false)
|
index: 0,
|
||||||
const [growOnly, setGrowOnly] = createSignal(true)
|
cycling: false,
|
||||||
|
growOnly: true,
|
||||||
const [duration, setDuration] = createSignal(600)
|
duration: 600,
|
||||||
const [bounce, setBounce] = createSignal(1.0)
|
bounce: 1.0,
|
||||||
const [bounceSoft, setBounceSoft] = createSignal(1.0)
|
bounceSoft: 1.0,
|
||||||
|
hybridTravel: 25,
|
||||||
const [hybridTravel, setHybridTravel] = createSignal(25)
|
hybridEdge: 17,
|
||||||
const [hybridEdge, setHybridEdge] = createSignal(17)
|
edge: 17,
|
||||||
|
revealTravel: 0,
|
||||||
const [edge, setEdge] = createSignal(17)
|
})
|
||||||
const [revealTravel, setRevealTravel] = createSignal(0)
|
const index = () => state.index
|
||||||
|
const cycling = () => state.cycling
|
||||||
|
const growOnly = () => state.growOnly
|
||||||
|
const duration = () => state.duration
|
||||||
|
const bounce = () => state.bounce
|
||||||
|
const bounceSoft = () => state.bounceSoft
|
||||||
|
const hybridTravel = () => state.hybridTravel
|
||||||
|
const hybridEdge = () => state.hybridEdge
|
||||||
|
const edge = () => state.edge
|
||||||
|
const revealTravel = () => state.revealTravel
|
||||||
|
|
||||||
let timer: number | undefined
|
let timer: number | undefined
|
||||||
const text = () => TEXTS[index()]
|
const text = () => TEXTS[index()]
|
||||||
const next = () => setIndex((i) => (i + 1) % TEXTS.length)
|
const next = () => setState("index", (value) => (value + 1) % TEXTS.length)
|
||||||
const prev = () => setIndex((i) => (i - 1 + TEXTS.length) % TEXTS.length)
|
const prev = () => setState("index", (value) => (value - 1 + TEXTS.length) % TEXTS.length)
|
||||||
|
|
||||||
const toggleCycle = () => {
|
const toggleCycle = () => {
|
||||||
if (cycling()) {
|
if (cycling()) {
|
||||||
if (timer) clearTimeout(timer)
|
if (timer) clearTimeout(timer)
|
||||||
timer = undefined
|
timer = undefined
|
||||||
setCycling(false)
|
setState("cycling", false)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
setCycling(true)
|
setState("cycling", true)
|
||||||
const tick = () => {
|
const tick = () => {
|
||||||
next()
|
next()
|
||||||
timer = window.setTimeout(tick, 700 + Math.floor(Math.random() * 600))
|
timer = window.setTimeout(tick, 700 + Math.floor(Math.random() * 600))
|
||||||
@@ -172,7 +182,7 @@ export const Playground = {
|
|||||||
|
|
||||||
<div style={{ display: "flex", gap: "6px", "flex-wrap": "wrap" }}>
|
<div style={{ display: "flex", gap: "6px", "flex-wrap": "wrap" }}>
|
||||||
{TEXTS.map((t, i) => (
|
{TEXTS.map((t, i) => (
|
||||||
<button onClick={() => setIndex(i)} style={btn(index() === i)}>
|
<button onClick={() => setState("index", i)} style={btn(index() === i)}>
|
||||||
{t ?? "(none)"}
|
{t ?? "(none)"}
|
||||||
</button>
|
</button>
|
||||||
))}
|
))}
|
||||||
@@ -188,7 +198,7 @@ export const Playground = {
|
|||||||
<button onClick={toggleCycle} style={btn(cycling())}>
|
<button onClick={toggleCycle} style={btn(cycling())}>
|
||||||
{cycling() ? "Stop cycle" : "Auto cycle"}
|
{cycling() ? "Stop cycle" : "Auto cycle"}
|
||||||
</button>
|
</button>
|
||||||
<button onClick={() => setGrowOnly((v) => !v)} style={btn(growOnly())}>
|
<button onClick={() => setState("growOnly", (value) => !value)} style={btn(growOnly())}>
|
||||||
{growOnly() ? "growOnly: on" : "growOnly: off"}
|
{growOnly() ? "growOnly: on" : "growOnly: off"}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -204,7 +214,7 @@ export const Playground = {
|
|||||||
max="40"
|
max="40"
|
||||||
step="1"
|
step="1"
|
||||||
value={hybridEdge()}
|
value={hybridEdge()}
|
||||||
onInput={(e) => setHybridEdge(e.currentTarget.valueAsNumber)}
|
onInput={(e) => setState("hybridEdge", e.currentTarget.valueAsNumber)}
|
||||||
style={{ flex: 1 }}
|
style={{ flex: 1 }}
|
||||||
/>
|
/>
|
||||||
<span style={{ width: "60px", "text-align": "right", "font-size": "12px" }}>{hybridEdge()}%</span>
|
<span style={{ width: "60px", "text-align": "right", "font-size": "12px" }}>{hybridEdge()}%</span>
|
||||||
@@ -218,7 +228,7 @@ export const Playground = {
|
|||||||
max="40"
|
max="40"
|
||||||
step="1"
|
step="1"
|
||||||
value={hybridTravel()}
|
value={hybridTravel()}
|
||||||
onInput={(e) => setHybridTravel(e.currentTarget.valueAsNumber)}
|
onInput={(e) => setState("hybridTravel", e.currentTarget.valueAsNumber)}
|
||||||
style={{ flex: 1 }}
|
style={{ flex: 1 }}
|
||||||
/>
|
/>
|
||||||
<span style={{ width: "60px", "text-align": "right", "font-size": "12px" }}>{hybridTravel()}px</span>
|
<span style={{ width: "60px", "text-align": "right", "font-size": "12px" }}>{hybridTravel()}px</span>
|
||||||
@@ -234,7 +244,7 @@ export const Playground = {
|
|||||||
max="1400"
|
max="1400"
|
||||||
step="10"
|
step="10"
|
||||||
value={duration()}
|
value={duration()}
|
||||||
onInput={(e) => setDuration(e.currentTarget.valueAsNumber)}
|
onInput={(e) => setState("duration", e.currentTarget.valueAsNumber)}
|
||||||
style={{ flex: 1 }}
|
style={{ flex: 1 }}
|
||||||
/>
|
/>
|
||||||
<span style={{ width: "60px", "text-align": "right", "font-size": "12px" }}>{duration()}ms</span>
|
<span style={{ width: "60px", "text-align": "right", "font-size": "12px" }}>{duration()}ms</span>
|
||||||
@@ -248,7 +258,7 @@ export const Playground = {
|
|||||||
max="2"
|
max="2"
|
||||||
step="0.01"
|
step="0.01"
|
||||||
value={bounce()}
|
value={bounce()}
|
||||||
onInput={(e) => setBounce(e.currentTarget.valueAsNumber)}
|
onInput={(e) => setState("bounce", e.currentTarget.valueAsNumber)}
|
||||||
style={{ flex: 1 }}
|
style={{ flex: 1 }}
|
||||||
/>
|
/>
|
||||||
<span style={{ width: "60px", "text-align": "right", "font-size": "12px" }}>{bounce().toFixed(2)}</span>
|
<span style={{ width: "60px", "text-align": "right", "font-size": "12px" }}>{bounce().toFixed(2)}</span>
|
||||||
@@ -262,7 +272,7 @@ export const Playground = {
|
|||||||
max="1.5"
|
max="1.5"
|
||||||
step="0.01"
|
step="0.01"
|
||||||
value={bounceSoft()}
|
value={bounceSoft()}
|
||||||
onInput={(e) => setBounceSoft(e.currentTarget.valueAsNumber)}
|
onInput={(e) => setState("bounceSoft", e.currentTarget.valueAsNumber)}
|
||||||
style={{ flex: 1 }}
|
style={{ flex: 1 }}
|
||||||
/>
|
/>
|
||||||
<span style={{ width: "60px", "text-align": "right", "font-size": "12px" }}>{bounceSoft().toFixed(2)}</span>
|
<span style={{ width: "60px", "text-align": "right", "font-size": "12px" }}>{bounceSoft().toFixed(2)}</span>
|
||||||
@@ -280,7 +290,7 @@ export const Playground = {
|
|||||||
max="40"
|
max="40"
|
||||||
step="1"
|
step="1"
|
||||||
value={edge()}
|
value={edge()}
|
||||||
onInput={(e) => setEdge(e.currentTarget.valueAsNumber)}
|
onInput={(e) => setState("edge", e.currentTarget.valueAsNumber)}
|
||||||
style={{ flex: 1 }}
|
style={{ flex: 1 }}
|
||||||
/>
|
/>
|
||||||
<span style={{ width: "60px", "text-align": "right", "font-size": "12px" }}>{edge()}%</span>
|
<span style={{ width: "60px", "text-align": "right", "font-size": "12px" }}>{edge()}%</span>
|
||||||
@@ -294,7 +304,7 @@ export const Playground = {
|
|||||||
max="16"
|
max="16"
|
||||||
step="1"
|
step="1"
|
||||||
value={revealTravel()}
|
value={revealTravel()}
|
||||||
onInput={(e) => setRevealTravel(e.currentTarget.valueAsNumber)}
|
onInput={(e) => setState("revealTravel", e.currentTarget.valueAsNumber)}
|
||||||
style={{ flex: 1 }}
|
style={{ flex: 1 }}
|
||||||
/>
|
/>
|
||||||
<span style={{ width: "60px", "text-align": "right", "font-size": "12px" }}>{revealTravel()}px</span>
|
<span style={{ width: "60px", "text-align": "right", "font-size": "12px" }}>{revealTravel()}px</span>
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { createEffect, createSignal, on, onCleanup, onMount } from "solid-js"
|
import { createEffect, on, onCleanup, onMount } from "solid-js"
|
||||||
|
import { createStore } from "solid-js/store"
|
||||||
|
|
||||||
const px = (value: number | string | undefined, fallback: number) => {
|
const px = (value: number | string | undefined, fallback: number) => {
|
||||||
if (typeof value === "number") return `${value}px`
|
if (typeof value === "number") return `${value}px`
|
||||||
@@ -30,11 +31,18 @@ export function TextReveal(props: {
|
|||||||
growOnly?: boolean
|
growOnly?: boolean
|
||||||
truncate?: boolean
|
truncate?: boolean
|
||||||
}) {
|
}) {
|
||||||
const [cur, setCur] = createSignal(props.text)
|
const [state, setState] = createStore({
|
||||||
const [old, setOld] = createSignal<string | undefined>()
|
cur: props.text,
|
||||||
const [width, setWidth] = createSignal("auto")
|
old: undefined as string | undefined,
|
||||||
const [ready, setReady] = createSignal(false)
|
width: "auto",
|
||||||
const [swapping, setSwapping] = createSignal(false)
|
ready: false,
|
||||||
|
swapping: false,
|
||||||
|
})
|
||||||
|
const cur = () => state.cur
|
||||||
|
const old = () => state.old
|
||||||
|
const width = () => state.width
|
||||||
|
const ready = () => state.ready
|
||||||
|
const swapping = () => state.swapping
|
||||||
let inRef: HTMLSpanElement | undefined
|
let inRef: HTMLSpanElement | undefined
|
||||||
let outRef: HTMLSpanElement | undefined
|
let outRef: HTMLSpanElement | undefined
|
||||||
let rootRef: HTMLSpanElement | undefined
|
let rootRef: HTMLSpanElement | undefined
|
||||||
@@ -49,7 +57,7 @@ export function TextReveal(props: {
|
|||||||
const prev = Number.parseFloat(width())
|
const prev = Number.parseFloat(width())
|
||||||
if (Number.isFinite(prev) && next <= prev) return
|
if (Number.isFinite(prev) && next <= prev) return
|
||||||
}
|
}
|
||||||
setWidth(`${next}px`)
|
setState("width", `${next}px`)
|
||||||
}
|
}
|
||||||
|
|
||||||
createEffect(
|
createEffect(
|
||||||
@@ -58,25 +66,25 @@ export function TextReveal(props: {
|
|||||||
(next, prev) => {
|
(next, prev) => {
|
||||||
if (next === prev) return
|
if (next === prev) return
|
||||||
if (typeof next === "string" && typeof prev === "string" && next.startsWith(prev)) {
|
if (typeof next === "string" && typeof prev === "string" && next.startsWith(prev)) {
|
||||||
setCur(next)
|
setState("cur", next)
|
||||||
widen(win())
|
widen(win())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
setSwapping(true)
|
setState("swapping", true)
|
||||||
setOld(prev)
|
setState("old", prev)
|
||||||
setCur(next)
|
setState("cur", next)
|
||||||
|
|
||||||
if (typeof requestAnimationFrame !== "function") {
|
if (typeof requestAnimationFrame !== "function") {
|
||||||
widen(Math.max(win(), wout()))
|
widen(Math.max(win(), wout()))
|
||||||
rootRef?.offsetHeight
|
rootRef?.offsetHeight
|
||||||
setSwapping(false)
|
setState("swapping", false)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (frame !== undefined && typeof cancelAnimationFrame === "function") cancelAnimationFrame(frame)
|
if (frame !== undefined && typeof cancelAnimationFrame === "function") cancelAnimationFrame(frame)
|
||||||
frame = requestAnimationFrame(() => {
|
frame = requestAnimationFrame(() => {
|
||||||
widen(Math.max(win(), wout()))
|
widen(Math.max(win(), wout()))
|
||||||
rootRef?.offsetHeight
|
rootRef?.offsetHeight
|
||||||
setSwapping(false)
|
setState("swapping", false)
|
||||||
frame = undefined
|
frame = undefined
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
@@ -87,16 +95,16 @@ export function TextReveal(props: {
|
|||||||
widen(win())
|
widen(win())
|
||||||
const fonts = typeof document !== "undefined" ? document.fonts : undefined
|
const fonts = typeof document !== "undefined" ? document.fonts : undefined
|
||||||
if (typeof requestAnimationFrame !== "function") {
|
if (typeof requestAnimationFrame !== "function") {
|
||||||
setReady(true)
|
setState("ready", true)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (!fonts) {
|
if (!fonts) {
|
||||||
requestAnimationFrame(() => setReady(true))
|
requestAnimationFrame(() => setState("ready", true))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
fonts.ready.finally(() => {
|
fonts.ready.finally(() => {
|
||||||
widen(win())
|
widen(win())
|
||||||
requestAnimationFrame(() => setReady(true))
|
requestAnimationFrame(() => setState("ready", true))
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
// @ts-nocheck
|
// @ts-nocheck
|
||||||
import { createEffect, createSignal, onCleanup, onMount } from "solid-js"
|
import { createEffect, createSignal, onCleanup, onMount } from "solid-js"
|
||||||
|
import { createStore } from "solid-js/store"
|
||||||
import { useSpring } from "./motion-spring"
|
import { useSpring } from "./motion-spring"
|
||||||
import { TextStrikethrough } from "./text-strikethrough"
|
import { TextStrikethrough } from "./text-strikethrough"
|
||||||
|
|
||||||
@@ -130,12 +131,16 @@ function VariantF(props: { active: boolean; text: string }) {
|
|||||||
)
|
)
|
||||||
let baseRef: HTMLSpanElement | undefined
|
let baseRef: HTMLSpanElement | undefined
|
||||||
let containerRef: HTMLSpanElement | undefined
|
let containerRef: HTMLSpanElement | undefined
|
||||||
const [textWidth, setTextWidth] = createSignal(0)
|
const [state, setState] = createStore({
|
||||||
const [containerWidth, setContainerWidth] = createSignal(0)
|
textWidth: 0,
|
||||||
|
containerWidth: 0,
|
||||||
|
})
|
||||||
|
const textWidth = () => state.textWidth
|
||||||
|
const containerWidth = () => state.containerWidth
|
||||||
|
|
||||||
const measure = () => {
|
const measure = () => {
|
||||||
if (baseRef) setTextWidth(baseRef.scrollWidth)
|
if (baseRef) setState("textWidth", baseRef.scrollWidth)
|
||||||
if (containerRef) setContainerWidth(containerRef.offsetWidth)
|
if (containerRef) setState("containerWidth", containerRef.offsetWidth)
|
||||||
}
|
}
|
||||||
|
|
||||||
onMount(measure)
|
onMount(measure)
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import type { JSX } from "solid-js"
|
import type { JSX } from "solid-js"
|
||||||
import { createEffect, createSignal, onCleanup, onMount } from "solid-js"
|
import { createEffect, onCleanup, onMount } from "solid-js"
|
||||||
|
import { createStore } from "solid-js/store"
|
||||||
import { useSpring } from "./motion-spring"
|
import { useSpring } from "./motion-spring"
|
||||||
|
|
||||||
export function TextStrikethrough(props: {
|
export function TextStrikethrough(props: {
|
||||||
@@ -19,12 +20,16 @@ export function TextStrikethrough(props: {
|
|||||||
|
|
||||||
let baseRef: HTMLSpanElement | undefined
|
let baseRef: HTMLSpanElement | undefined
|
||||||
let containerRef: HTMLSpanElement | undefined
|
let containerRef: HTMLSpanElement | undefined
|
||||||
const [textWidth, setTextWidth] = createSignal(0)
|
const [state, setState] = createStore({
|
||||||
const [containerWidth, setContainerWidth] = createSignal(0)
|
textWidth: 0,
|
||||||
|
containerWidth: 0,
|
||||||
|
})
|
||||||
|
const textWidth = () => state.textWidth
|
||||||
|
const containerWidth = () => state.containerWidth
|
||||||
|
|
||||||
const measure = () => {
|
const measure = () => {
|
||||||
if (baseRef) setTextWidth(baseRef.scrollWidth)
|
if (baseRef) setState("textWidth", baseRef.scrollWidth)
|
||||||
if (containerRef) setContainerWidth(containerRef.offsetWidth)
|
if (containerRef) setState("containerWidth", containerRef.offsetWidth)
|
||||||
}
|
}
|
||||||
|
|
||||||
onMount(measure)
|
onMount(measure)
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
// @ts-nocheck
|
// @ts-nocheck
|
||||||
import { createSignal, createEffect, on, onMount, onCleanup } from "solid-js"
|
import { createEffect, on, onMount, onCleanup } from "solid-js"
|
||||||
|
import { createStore } from "solid-js/store"
|
||||||
import { TextShimmer } from "./text-shimmer"
|
import { TextShimmer } from "./text-shimmer"
|
||||||
import { TextReveal } from "./text-reveal"
|
import { TextReveal } from "./text-reveal"
|
||||||
|
|
||||||
@@ -375,11 +376,18 @@ input[type="range"].heading-slider::-webkit-slider-thumb {
|
|||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
function AnimatedHeading(props) {
|
function AnimatedHeading(props) {
|
||||||
const [current, setCurrent] = createSignal(props.text)
|
const [state, setState] = createStore({
|
||||||
const [leaving, setLeaving] = createSignal(undefined)
|
current: props.text,
|
||||||
const [width, setWidth] = createSignal("auto")
|
leaving: undefined,
|
||||||
const [ready, setReady] = createSignal(false)
|
width: "auto",
|
||||||
const [swapping, setSwapping] = createSignal(false)
|
ready: false,
|
||||||
|
swapping: false,
|
||||||
|
})
|
||||||
|
const current = () => state.current
|
||||||
|
const leaving = () => state.leaving
|
||||||
|
const width = () => state.width
|
||||||
|
const ready = () => state.ready
|
||||||
|
const swapping = () => state.swapping
|
||||||
let enterRef
|
let enterRef
|
||||||
let leaveRef
|
let leaveRef
|
||||||
let containerRef
|
let containerRef
|
||||||
@@ -391,16 +399,16 @@ function AnimatedHeading(props) {
|
|||||||
if (px <= 0) return
|
if (px <= 0) return
|
||||||
const w = Number.parseFloat(width())
|
const w = Number.parseFloat(width())
|
||||||
if (Number.isFinite(w) && px <= w) return
|
if (Number.isFinite(w) && px <= w) return
|
||||||
setWidth(`${px}px`)
|
setState("width", `${px}px`)
|
||||||
}
|
}
|
||||||
|
|
||||||
const measure = () => {
|
const measure = () => {
|
||||||
if (!current()) {
|
if (!current()) {
|
||||||
setWidth("0px")
|
setState("width", "0px")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
const px = measureEnter()
|
const px = measureEnter()
|
||||||
if (px > 0) setWidth(`${px}px`)
|
if (px > 0) setState("width", `${px}px`)
|
||||||
}
|
}
|
||||||
|
|
||||||
createEffect(
|
createEffect(
|
||||||
@@ -408,9 +416,9 @@ function AnimatedHeading(props) {
|
|||||||
() => props.text,
|
() => props.text,
|
||||||
(next, prev) => {
|
(next, prev) => {
|
||||||
if (next === prev) return
|
if (next === prev) return
|
||||||
setSwapping(true)
|
setState("swapping", true)
|
||||||
setLeaving(prev)
|
setState("leaving", prev)
|
||||||
setCurrent(next)
|
setState("current", next)
|
||||||
|
|
||||||
if (frame) cancelAnimationFrame(frame)
|
if (frame) cancelAnimationFrame(frame)
|
||||||
frame = requestAnimationFrame(() => {
|
frame = requestAnimationFrame(() => {
|
||||||
@@ -420,10 +428,10 @@ function AnimatedHeading(props) {
|
|||||||
const leaveW = measureLeave()
|
const leaveW = measureLeave()
|
||||||
widen(Math.max(enterW, leaveW))
|
widen(Math.max(enterW, leaveW))
|
||||||
containerRef?.offsetHeight // reflow with max width + swap positions
|
containerRef?.offsetHeight // reflow with max width + swap positions
|
||||||
setSwapping(false)
|
setState("swapping", false)
|
||||||
} else {
|
} else {
|
||||||
containerRef?.offsetHeight
|
containerRef?.offsetHeight
|
||||||
setSwapping(false)
|
setState("swapping", false)
|
||||||
measure()
|
measure()
|
||||||
}
|
}
|
||||||
frame = undefined
|
frame = undefined
|
||||||
@@ -436,7 +444,7 @@ function AnimatedHeading(props) {
|
|||||||
measure()
|
measure()
|
||||||
document.fonts?.ready.finally(() => {
|
document.fonts?.ready.finally(() => {
|
||||||
measure()
|
measure()
|
||||||
requestAnimationFrame(() => setReady(true))
|
requestAnimationFrame(() => setState("ready", true))
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -552,47 +560,56 @@ const VARIANTS: { key: string; label: string }[] = []
|
|||||||
|
|
||||||
export const Playground = {
|
export const Playground = {
|
||||||
render: () => {
|
render: () => {
|
||||||
const [heading, setHeading] = createSignal(HEADINGS[0])
|
const [state, setState] = createStore({
|
||||||
const [headingIndex, setHeadingIndex] = createSignal(0)
|
heading: HEADINGS[0],
|
||||||
const [active, setActive] = createSignal(true)
|
headingIndex: 0,
|
||||||
const [cycling, setCycling] = createSignal(false)
|
active: true,
|
||||||
|
cycling: false,
|
||||||
|
duration: 550,
|
||||||
|
blur: 2,
|
||||||
|
travel: 4,
|
||||||
|
bounce: 1.35,
|
||||||
|
maskSize: 12,
|
||||||
|
maskPad: 9,
|
||||||
|
maskHeight: 0,
|
||||||
|
debug: false,
|
||||||
|
odoBlur: false,
|
||||||
|
})
|
||||||
|
const heading = () => state.heading
|
||||||
|
const headingIndex = () => state.headingIndex
|
||||||
|
const active = () => state.active
|
||||||
|
const cycling = () => state.cycling
|
||||||
|
const duration = () => state.duration
|
||||||
|
const blur = () => state.blur
|
||||||
|
const travel = () => state.travel
|
||||||
|
const bounce = () => state.bounce
|
||||||
|
const maskSize = () => state.maskSize
|
||||||
|
const maskPad = () => state.maskPad
|
||||||
|
const maskHeight = () => state.maskHeight
|
||||||
|
const debug = () => state.debug
|
||||||
|
const odoBlur = () => state.odoBlur
|
||||||
let cycleTimer
|
let cycleTimer
|
||||||
|
|
||||||
// tunable params
|
|
||||||
const [duration, setDuration] = createSignal(550)
|
|
||||||
const [blur, setBlur] = createSignal(2)
|
|
||||||
const [travel, setTravel] = createSignal(4)
|
|
||||||
const [bounce, setBounce] = createSignal(1.35)
|
|
||||||
const [maskSize, setMaskSize] = createSignal(12)
|
|
||||||
const [maskPad, setMaskPad] = createSignal(9)
|
|
||||||
const [maskHeight, setMaskHeight] = createSignal(0)
|
|
||||||
const [debug, setDebug] = createSignal(false)
|
|
||||||
const [odoBlur, setOdoBlur] = createSignal(false)
|
|
||||||
|
|
||||||
const nextHeading = () => {
|
const nextHeading = () => {
|
||||||
setHeadingIndex((i) => {
|
const next = (headingIndex() + 1) % HEADINGS.length
|
||||||
const next = (i + 1) % HEADINGS.length
|
setState("headingIndex", next)
|
||||||
setHeading(HEADINGS[next])
|
setState("heading", HEADINGS[next])
|
||||||
return next
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const prevHeading = () => {
|
const prevHeading = () => {
|
||||||
setHeadingIndex((i) => {
|
const prev = (headingIndex() - 1 + HEADINGS.length) % HEADINGS.length
|
||||||
const prev = (i - 1 + HEADINGS.length) % HEADINGS.length
|
setState("headingIndex", prev)
|
||||||
setHeading(HEADINGS[prev])
|
setState("heading", HEADINGS[prev])
|
||||||
return prev
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const toggleCycling = () => {
|
const toggleCycling = () => {
|
||||||
if (cycling()) {
|
if (cycling()) {
|
||||||
clearTimeout(cycleTimer)
|
clearTimeout(cycleTimer)
|
||||||
cycleTimer = undefined
|
cycleTimer = undefined
|
||||||
setCycling(false)
|
setState("cycling", false)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
setCycling(true)
|
setState("cycling", true)
|
||||||
const tick = () => {
|
const tick = () => {
|
||||||
if (!cycling()) return
|
if (!cycling()) return
|
||||||
nextHeading()
|
nextHeading()
|
||||||
@@ -602,11 +619,11 @@ export const Playground = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const clearHeading = () => {
|
const clearHeading = () => {
|
||||||
setHeading(undefined)
|
setState("heading", undefined)
|
||||||
if (cycling()) {
|
if (cycling()) {
|
||||||
clearTimeout(cycleTimer)
|
clearTimeout(cycleTimer)
|
||||||
cycleTimer = undefined
|
cycleTimer = undefined
|
||||||
setCycling(false)
|
setState("cycling", false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -686,7 +703,7 @@ export const Playground = {
|
|||||||
max={1400}
|
max={1400}
|
||||||
step={50}
|
step={50}
|
||||||
value={duration()}
|
value={duration()}
|
||||||
onInput={(e) => setDuration(Number(e.currentTarget.value))}
|
onInput={(e) => setState("duration", Number(e.currentTarget.value))}
|
||||||
/>
|
/>
|
||||||
<span style={sliderValue}>{duration()}ms</span>
|
<span style={sliderValue}>{duration()}ms</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -700,7 +717,7 @@ export const Playground = {
|
|||||||
max={16}
|
max={16}
|
||||||
step={0.5}
|
step={0.5}
|
||||||
value={blur()}
|
value={blur()}
|
||||||
onInput={(e) => setBlur(Number(e.currentTarget.value))}
|
onInput={(e) => setState("blur", Number(e.currentTarget.value))}
|
||||||
/>
|
/>
|
||||||
<span style={sliderValue}>{blur()}px</span>
|
<span style={sliderValue}>{blur()}px</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -714,7 +731,7 @@ export const Playground = {
|
|||||||
max={120}
|
max={120}
|
||||||
step={1}
|
step={1}
|
||||||
value={travel()}
|
value={travel()}
|
||||||
onInput={(e) => setTravel(Number(e.currentTarget.value))}
|
onInput={(e) => setState("travel", Number(e.currentTarget.value))}
|
||||||
/>
|
/>
|
||||||
<span style={sliderValue}>{travel()}px</span>
|
<span style={sliderValue}>{travel()}px</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -728,7 +745,7 @@ export const Playground = {
|
|||||||
max={2.2}
|
max={2.2}
|
||||||
step={0.05}
|
step={0.05}
|
||||||
value={bounce()}
|
value={bounce()}
|
||||||
onInput={(e) => setBounce(Number(e.currentTarget.value))}
|
onInput={(e) => setState("bounce", Number(e.currentTarget.value))}
|
||||||
/>
|
/>
|
||||||
<span style={sliderValue}>
|
<span style={sliderValue}>
|
||||||
{bounce().toFixed(2)} {bounce() <= 1.05 ? "(none)" : bounce() >= 1.9 ? "(heavy)" : ""}
|
{bounce().toFixed(2)} {bounce() <= 1.05 ? "(none)" : bounce() >= 1.9 ? "(heavy)" : ""}
|
||||||
@@ -744,7 +761,7 @@ export const Playground = {
|
|||||||
max={50}
|
max={50}
|
||||||
step={1}
|
step={1}
|
||||||
value={maskSize()}
|
value={maskSize()}
|
||||||
onInput={(e) => setMaskSize(Number(e.currentTarget.value))}
|
onInput={(e) => setState("maskSize", Number(e.currentTarget.value))}
|
||||||
/>
|
/>
|
||||||
<span style={sliderValue}>
|
<span style={sliderValue}>
|
||||||
{maskSize()}px {maskSize() === 0 ? "(hard)" : ""}
|
{maskSize()}px {maskSize() === 0 ? "(hard)" : ""}
|
||||||
@@ -760,7 +777,7 @@ export const Playground = {
|
|||||||
max={60}
|
max={60}
|
||||||
step={1}
|
step={1}
|
||||||
value={maskPad()}
|
value={maskPad()}
|
||||||
onInput={(e) => setMaskPad(Number(e.currentTarget.value))}
|
onInput={(e) => setState("maskPad", Number(e.currentTarget.value))}
|
||||||
/>
|
/>
|
||||||
<span style={sliderValue}>{maskPad()}px</span>
|
<span style={sliderValue}>{maskPad()}px</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -774,7 +791,7 @@ export const Playground = {
|
|||||||
max={80}
|
max={80}
|
||||||
step={1}
|
step={1}
|
||||||
value={maskHeight()}
|
value={maskHeight()}
|
||||||
onInput={(e) => setMaskHeight(Number(e.currentTarget.value))}
|
onInput={(e) => setState("maskHeight", Number(e.currentTarget.value))}
|
||||||
/>
|
/>
|
||||||
<span style={sliderValue}>{maskHeight()}px</span>
|
<span style={sliderValue}>{maskHeight()}px</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -795,13 +812,13 @@ export const Playground = {
|
|||||||
<button onClick={clearHeading} style={btn()}>
|
<button onClick={clearHeading} style={btn()}>
|
||||||
Clear
|
Clear
|
||||||
</button>
|
</button>
|
||||||
<button onClick={() => setActive((v) => !v)} style={smallBtn(active())}>
|
<button onClick={() => setState("active", (value) => !value)} style={smallBtn(active())}>
|
||||||
{active() ? "Shimmer: on" : "Shimmer: off"}
|
{active() ? "Shimmer: on" : "Shimmer: off"}
|
||||||
</button>
|
</button>
|
||||||
<button onClick={() => setDebug((v) => !v)} style={smallBtn(debug())}>
|
<button onClick={() => setState("debug", (value) => !value)} style={smallBtn(debug())}>
|
||||||
{debug() ? "Debug mask: on" : "Debug mask"}
|
{debug() ? "Debug mask: on" : "Debug mask"}
|
||||||
</button>
|
</button>
|
||||||
<button onClick={() => setOdoBlur((v) => !v)} style={smallBtn(odoBlur())}>
|
<button onClick={() => setState("odoBlur", (value) => !value)} style={smallBtn(odoBlur())}>
|
||||||
{odoBlur() ? "Odo blur: on" : "Odo blur"}
|
{odoBlur() ? "Odo blur: on" : "Odo blur"}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -810,8 +827,8 @@ export const Playground = {
|
|||||||
{HEADINGS.map((h, i) => (
|
{HEADINGS.map((h, i) => (
|
||||||
<button
|
<button
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setHeadingIndex(i)
|
setState("headingIndex", i)
|
||||||
setHeading(h)
|
setState("heading", h)
|
||||||
}}
|
}}
|
||||||
style={smallBtn(headingIndex() === i)}
|
style={smallBtn(headingIndex() === i)}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
// @ts-nocheck
|
// @ts-nocheck
|
||||||
import { createEffect, createMemo, createSignal, onCleanup } from "solid-js"
|
import { createEffect, createMemo, onCleanup } from "solid-js"
|
||||||
|
import { createStore } from "solid-js/store"
|
||||||
import type { Todo } from "@opencode-ai/sdk/v2"
|
import type { Todo } from "@opencode-ai/sdk/v2"
|
||||||
import { useGlobalSync } from "@/context/global-sync"
|
import { useGlobalSync } from "@/context/global-sync"
|
||||||
import { SessionComposerRegion, createSessionComposerState } from "@/pages/session/composer"
|
import { SessionComposerRegion, createSessionComposerState } from "@/pages/session/composer"
|
||||||
@@ -129,24 +130,44 @@ const css = `
|
|||||||
export const Playground = {
|
export const Playground = {
|
||||||
render: () => {
|
render: () => {
|
||||||
const global = useGlobalSync()
|
const global = useGlobalSync()
|
||||||
const [open, setOpen] = createSignal(true)
|
const [cfg, setCfg] = createStore({
|
||||||
const [step, setStep] = createSignal(1)
|
open: true,
|
||||||
const [dockOpenDuration, setDockOpenDuration] = createSignal(0.3)
|
step: 1,
|
||||||
const [dockOpenBounce, setDockOpenBounce] = createSignal(0)
|
dockOpenDuration: 0.3,
|
||||||
const [dockCloseDuration, setDockCloseDuration] = createSignal(0.3)
|
dockOpenBounce: 0,
|
||||||
const [dockCloseBounce, setDockCloseBounce] = createSignal(0)
|
dockCloseDuration: 0.3,
|
||||||
const [drawerExpandDuration, setDrawerExpandDuration] = createSignal(0.3)
|
dockCloseBounce: 0,
|
||||||
const [drawerExpandBounce, setDrawerExpandBounce] = createSignal(0)
|
drawerExpandDuration: 0.3,
|
||||||
const [drawerCollapseDuration, setDrawerCollapseDuration] = createSignal(0.3)
|
drawerExpandBounce: 0,
|
||||||
const [drawerCollapseBounce, setDrawerCollapseBounce] = createSignal(0)
|
drawerCollapseDuration: 0.3,
|
||||||
const [subtitleDuration, setSubtitleDuration] = createSignal(600)
|
drawerCollapseBounce: 0,
|
||||||
const [subtitleAuto, setSubtitleAuto] = createSignal(true)
|
subtitleDuration: 600,
|
||||||
const [subtitleTravel, setSubtitleTravel] = createSignal(25)
|
subtitleAuto: true,
|
||||||
const [subtitleEdge, setSubtitleEdge] = createSignal(17)
|
subtitleTravel: 25,
|
||||||
const [countDuration, setCountDuration] = createSignal(600)
|
subtitleEdge: 17,
|
||||||
const [countMask, setCountMask] = createSignal(18)
|
countDuration: 600,
|
||||||
const [countMaskHeight, setCountMaskHeight] = createSignal(0)
|
countMask: 18,
|
||||||
const [countWidthDuration, setCountWidthDuration] = createSignal(560)
|
countMaskHeight: 0,
|
||||||
|
countWidthDuration: 560,
|
||||||
|
})
|
||||||
|
const open = () => cfg.open
|
||||||
|
const step = () => cfg.step
|
||||||
|
const dockOpenDuration = () => cfg.dockOpenDuration
|
||||||
|
const dockOpenBounce = () => cfg.dockOpenBounce
|
||||||
|
const dockCloseDuration = () => cfg.dockCloseDuration
|
||||||
|
const dockCloseBounce = () => cfg.dockCloseBounce
|
||||||
|
const drawerExpandDuration = () => cfg.drawerExpandDuration
|
||||||
|
const drawerExpandBounce = () => cfg.drawerExpandBounce
|
||||||
|
const drawerCollapseDuration = () => cfg.drawerCollapseDuration
|
||||||
|
const drawerCollapseBounce = () => cfg.drawerCollapseBounce
|
||||||
|
const subtitleDuration = () => cfg.subtitleDuration
|
||||||
|
const subtitleAuto = () => cfg.subtitleAuto
|
||||||
|
const subtitleTravel = () => cfg.subtitleTravel
|
||||||
|
const subtitleEdge = () => cfg.subtitleEdge
|
||||||
|
const countDuration = () => cfg.countDuration
|
||||||
|
const countMask = () => cfg.countMask
|
||||||
|
const countMaskHeight = () => cfg.countMaskHeight
|
||||||
|
const countWidthDuration = () => cfg.countWidthDuration
|
||||||
const state = createSessionComposerState({ closeMs: () => Math.round(dockCloseDuration() * 1000) })
|
const state = createSessionComposerState({ closeMs: () => Math.round(dockCloseDuration() * 1000) })
|
||||||
let frame
|
let frame
|
||||||
let composerRef
|
let composerRef
|
||||||
@@ -187,7 +208,7 @@ export const Playground = {
|
|||||||
|
|
||||||
const openDock = () => {
|
const openDock = () => {
|
||||||
clear()
|
clear()
|
||||||
setOpen(true)
|
setCfg("open", true)
|
||||||
frame = requestAnimationFrame(() => {
|
frame = requestAnimationFrame(() => {
|
||||||
pin()
|
pin()
|
||||||
frame = undefined
|
frame = undefined
|
||||||
@@ -196,7 +217,7 @@ export const Playground = {
|
|||||||
|
|
||||||
const closeDock = () => {
|
const closeDock = () => {
|
||||||
clear()
|
clear()
|
||||||
setOpen(false)
|
setCfg("open", false)
|
||||||
}
|
}
|
||||||
|
|
||||||
const dockOpen = () => open()
|
const dockOpen = () => open()
|
||||||
@@ -223,7 +244,7 @@ export const Playground = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const cycle = () => {
|
const cycle = () => {
|
||||||
setStep((value) => (value + 1) % 4)
|
setCfg("step", (value) => (value + 1) % 4)
|
||||||
}
|
}
|
||||||
|
|
||||||
onCleanup(clear)
|
onCleanup(clear)
|
||||||
@@ -289,7 +310,7 @@ export const Playground = {
|
|||||||
Cycle progress ({step()}/3 done)
|
Cycle progress ({step()}/3 done)
|
||||||
</button>
|
</button>
|
||||||
{[0, 1, 2, 3].map((value) => (
|
{[0, 1, 2, 3].map((value) => (
|
||||||
<button onClick={() => setStep(value)} style={btn(step() === value)}>
|
<button onClick={() => setCfg("step", value)} style={btn(step() === value)}>
|
||||||
{value} done
|
{value} done
|
||||||
</button>
|
</button>
|
||||||
))}
|
))}
|
||||||
@@ -307,7 +328,7 @@ export const Playground = {
|
|||||||
max="1"
|
max="1"
|
||||||
step="0.01"
|
step="0.01"
|
||||||
value={dockOpenDuration()}
|
value={dockOpenDuration()}
|
||||||
onInput={(event) => setDockOpenDuration(event.currentTarget.valueAsNumber)}
|
onInput={(event) => setCfg("dockOpenDuration", event.currentTarget.valueAsNumber)}
|
||||||
style={{ flex: 1 }}
|
style={{ flex: 1 }}
|
||||||
/>
|
/>
|
||||||
<span style={{ width: "64px", "text-align": "right", "font-size": "13px" }}>
|
<span style={{ width: "64px", "text-align": "right", "font-size": "13px" }}>
|
||||||
@@ -324,7 +345,7 @@ export const Playground = {
|
|||||||
max="1"
|
max="1"
|
||||||
step="0.01"
|
step="0.01"
|
||||||
value={dockOpenBounce()}
|
value={dockOpenBounce()}
|
||||||
onInput={(event) => setDockOpenBounce(event.currentTarget.valueAsNumber)}
|
onInput={(event) => setCfg("dockOpenBounce", event.currentTarget.valueAsNumber)}
|
||||||
style={{ flex: 1 }}
|
style={{ flex: 1 }}
|
||||||
/>
|
/>
|
||||||
<span style={{ width: "64px", "text-align": "right", "font-size": "13px" }}>
|
<span style={{ width: "64px", "text-align": "right", "font-size": "13px" }}>
|
||||||
@@ -345,7 +366,7 @@ export const Playground = {
|
|||||||
max="1"
|
max="1"
|
||||||
step="0.01"
|
step="0.01"
|
||||||
value={dockCloseDuration()}
|
value={dockCloseDuration()}
|
||||||
onInput={(event) => setDockCloseDuration(event.currentTarget.valueAsNumber)}
|
onInput={(event) => setCfg("dockCloseDuration", event.currentTarget.valueAsNumber)}
|
||||||
style={{ flex: 1 }}
|
style={{ flex: 1 }}
|
||||||
/>
|
/>
|
||||||
<span style={{ width: "64px", "text-align": "right", "font-size": "13px" }}>
|
<span style={{ width: "64px", "text-align": "right", "font-size": "13px" }}>
|
||||||
@@ -362,7 +383,7 @@ export const Playground = {
|
|||||||
max="1"
|
max="1"
|
||||||
step="0.01"
|
step="0.01"
|
||||||
value={dockCloseBounce()}
|
value={dockCloseBounce()}
|
||||||
onInput={(event) => setDockCloseBounce(event.currentTarget.valueAsNumber)}
|
onInput={(event) => setCfg("dockCloseBounce", event.currentTarget.valueAsNumber)}
|
||||||
style={{ flex: 1 }}
|
style={{ flex: 1 }}
|
||||||
/>
|
/>
|
||||||
<span style={{ width: "64px", "text-align": "right", "font-size": "13px" }}>
|
<span style={{ width: "64px", "text-align": "right", "font-size": "13px" }}>
|
||||||
@@ -383,7 +404,7 @@ export const Playground = {
|
|||||||
max="1"
|
max="1"
|
||||||
step="0.01"
|
step="0.01"
|
||||||
value={drawerExpandDuration()}
|
value={drawerExpandDuration()}
|
||||||
onInput={(event) => setDrawerExpandDuration(event.currentTarget.valueAsNumber)}
|
onInput={(event) => setCfg("drawerExpandDuration", event.currentTarget.valueAsNumber)}
|
||||||
style={{ flex: 1 }}
|
style={{ flex: 1 }}
|
||||||
/>
|
/>
|
||||||
<span style={{ width: "64px", "text-align": "right", "font-size": "13px" }}>
|
<span style={{ width: "64px", "text-align": "right", "font-size": "13px" }}>
|
||||||
@@ -400,7 +421,7 @@ export const Playground = {
|
|||||||
max="1"
|
max="1"
|
||||||
step="0.01"
|
step="0.01"
|
||||||
value={drawerExpandBounce()}
|
value={drawerExpandBounce()}
|
||||||
onInput={(event) => setDrawerExpandBounce(event.currentTarget.valueAsNumber)}
|
onInput={(event) => setCfg("drawerExpandBounce", event.currentTarget.valueAsNumber)}
|
||||||
style={{ flex: 1 }}
|
style={{ flex: 1 }}
|
||||||
/>
|
/>
|
||||||
<span style={{ width: "64px", "text-align": "right", "font-size": "13px" }}>
|
<span style={{ width: "64px", "text-align": "right", "font-size": "13px" }}>
|
||||||
@@ -421,7 +442,7 @@ export const Playground = {
|
|||||||
max="1"
|
max="1"
|
||||||
step="0.01"
|
step="0.01"
|
||||||
value={drawerCollapseDuration()}
|
value={drawerCollapseDuration()}
|
||||||
onInput={(event) => setDrawerCollapseDuration(event.currentTarget.valueAsNumber)}
|
onInput={(event) => setCfg("drawerCollapseDuration", event.currentTarget.valueAsNumber)}
|
||||||
style={{ flex: 1 }}
|
style={{ flex: 1 }}
|
||||||
/>
|
/>
|
||||||
<span style={{ width: "64px", "text-align": "right", "font-size": "13px" }}>
|
<span style={{ width: "64px", "text-align": "right", "font-size": "13px" }}>
|
||||||
@@ -438,7 +459,7 @@ export const Playground = {
|
|||||||
max="1"
|
max="1"
|
||||||
step="0.01"
|
step="0.01"
|
||||||
value={drawerCollapseBounce()}
|
value={drawerCollapseBounce()}
|
||||||
onInput={(event) => setDrawerCollapseBounce(event.currentTarget.valueAsNumber)}
|
onInput={(event) => setCfg("drawerCollapseBounce", event.currentTarget.valueAsNumber)}
|
||||||
style={{ flex: 1 }}
|
style={{ flex: 1 }}
|
||||||
/>
|
/>
|
||||||
<span style={{ width: "64px", "text-align": "right", "font-size": "13px" }}>
|
<span style={{ width: "64px", "text-align": "right", "font-size": "13px" }}>
|
||||||
@@ -459,7 +480,7 @@ export const Playground = {
|
|||||||
max="1400"
|
max="1400"
|
||||||
step="10"
|
step="10"
|
||||||
value={subtitleDuration()}
|
value={subtitleDuration()}
|
||||||
onInput={(event) => setSubtitleDuration(event.currentTarget.valueAsNumber)}
|
onInput={(event) => setCfg("subtitleDuration", event.currentTarget.valueAsNumber)}
|
||||||
style={{ flex: 1 }}
|
style={{ flex: 1 }}
|
||||||
/>
|
/>
|
||||||
<span style={{ width: "64px", "text-align": "right", "font-size": "13px" }}>
|
<span style={{ width: "64px", "text-align": "right", "font-size": "13px" }}>
|
||||||
@@ -473,7 +494,7 @@ export const Playground = {
|
|||||||
<input
|
<input
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
checked={subtitleAuto()}
|
checked={subtitleAuto()}
|
||||||
onInput={(event) => setSubtitleAuto(event.currentTarget.checked)}
|
onInput={(event) => setCfg("subtitleAuto", event.currentTarget.checked)}
|
||||||
/>
|
/>
|
||||||
<span style={{ width: "64px", "text-align": "right", "font-size": "13px" }}>
|
<span style={{ width: "64px", "text-align": "right", "font-size": "13px" }}>
|
||||||
{subtitleAuto() ? "on" : "off"}
|
{subtitleAuto() ? "on" : "off"}
|
||||||
@@ -489,7 +510,7 @@ export const Playground = {
|
|||||||
max="40"
|
max="40"
|
||||||
step="1"
|
step="1"
|
||||||
value={subtitleTravel()}
|
value={subtitleTravel()}
|
||||||
onInput={(event) => setSubtitleTravel(event.currentTarget.valueAsNumber)}
|
onInput={(event) => setCfg("subtitleTravel", event.currentTarget.valueAsNumber)}
|
||||||
style={{ flex: 1 }}
|
style={{ flex: 1 }}
|
||||||
/>
|
/>
|
||||||
<span style={{ width: "64px", "text-align": "right", "font-size": "13px" }}>{subtitleTravel()}px</span>
|
<span style={{ width: "64px", "text-align": "right", "font-size": "13px" }}>{subtitleTravel()}px</span>
|
||||||
@@ -504,7 +525,7 @@ export const Playground = {
|
|||||||
max="40"
|
max="40"
|
||||||
step="1"
|
step="1"
|
||||||
value={subtitleEdge()}
|
value={subtitleEdge()}
|
||||||
onInput={(event) => setSubtitleEdge(event.currentTarget.valueAsNumber)}
|
onInput={(event) => setCfg("subtitleEdge", event.currentTarget.valueAsNumber)}
|
||||||
style={{ flex: 1 }}
|
style={{ flex: 1 }}
|
||||||
/>
|
/>
|
||||||
<span style={{ width: "64px", "text-align": "right", "font-size": "13px" }}>{subtitleEdge()}%</span>
|
<span style={{ width: "64px", "text-align": "right", "font-size": "13px" }}>{subtitleEdge()}%</span>
|
||||||
@@ -523,7 +544,7 @@ export const Playground = {
|
|||||||
max="1400"
|
max="1400"
|
||||||
step="10"
|
step="10"
|
||||||
value={countDuration()}
|
value={countDuration()}
|
||||||
onInput={(event) => setCountDuration(event.currentTarget.valueAsNumber)}
|
onInput={(event) => setCfg("countDuration", event.currentTarget.valueAsNumber)}
|
||||||
style={{ flex: 1 }}
|
style={{ flex: 1 }}
|
||||||
/>
|
/>
|
||||||
<span style={{ width: "64px", "text-align": "right", "font-size": "13px" }}>
|
<span style={{ width: "64px", "text-align": "right", "font-size": "13px" }}>
|
||||||
@@ -540,7 +561,7 @@ export const Playground = {
|
|||||||
max="40"
|
max="40"
|
||||||
step="1"
|
step="1"
|
||||||
value={countMask()}
|
value={countMask()}
|
||||||
onInput={(event) => setCountMask(event.currentTarget.valueAsNumber)}
|
onInput={(event) => setCfg("countMask", event.currentTarget.valueAsNumber)}
|
||||||
style={{ flex: 1 }}
|
style={{ flex: 1 }}
|
||||||
/>
|
/>
|
||||||
<span style={{ width: "64px", "text-align": "right", "font-size": "13px" }}>{countMask()}%</span>
|
<span style={{ width: "64px", "text-align": "right", "font-size": "13px" }}>{countMask()}%</span>
|
||||||
@@ -555,7 +576,7 @@ export const Playground = {
|
|||||||
max="14"
|
max="14"
|
||||||
step="1"
|
step="1"
|
||||||
value={countMaskHeight()}
|
value={countMaskHeight()}
|
||||||
onInput={(event) => setCountMaskHeight(event.currentTarget.valueAsNumber)}
|
onInput={(event) => setCfg("countMaskHeight", event.currentTarget.valueAsNumber)}
|
||||||
style={{ flex: 1 }}
|
style={{ flex: 1 }}
|
||||||
/>
|
/>
|
||||||
<span style={{ width: "64px", "text-align": "right", "font-size": "13px" }}>{countMaskHeight()}px</span>
|
<span style={{ width: "64px", "text-align": "right", "font-size": "13px" }}>{countMaskHeight()}px</span>
|
||||||
@@ -570,7 +591,7 @@ export const Playground = {
|
|||||||
max="1200"
|
max="1200"
|
||||||
step="10"
|
step="10"
|
||||||
value={countWidthDuration()}
|
value={countWidthDuration()}
|
||||||
onInput={(event) => setCountWidthDuration(event.currentTarget.valueAsNumber)}
|
onInput={(event) => setCfg("countWidthDuration", event.currentTarget.valueAsNumber)}
|
||||||
style={{ flex: 1 }}
|
style={{ flex: 1 }}
|
||||||
/>
|
/>
|
||||||
<span style={{ width: "64px", "text-align": "right", "font-size": "13px" }}>
|
<span style={{ width: "64px", "text-align": "right", "font-size": "13px" }}>
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
// @ts-nocheck
|
// @ts-nocheck
|
||||||
import { createSignal, onCleanup } from "solid-js"
|
import { onCleanup } from "solid-js"
|
||||||
|
import { createStore } from "solid-js/store"
|
||||||
import { AnimatedCountList, type CountItem } from "./tool-count-summary"
|
import { AnimatedCountList, type CountItem } from "./tool-count-summary"
|
||||||
import { ToolStatusTitle } from "./tool-status-title"
|
import { ToolStatusTitle } from "./tool-status-title"
|
||||||
|
|
||||||
@@ -57,11 +58,18 @@ const smallBtn = (active?: boolean) =>
|
|||||||
|
|
||||||
export const Playground = {
|
export const Playground = {
|
||||||
render: () => {
|
render: () => {
|
||||||
const [reads, setReads] = createSignal(0)
|
const [state, setState] = createStore({
|
||||||
const [searches, setSearches] = createSignal(0)
|
reads: 0,
|
||||||
const [lists, setLists] = createSignal(0)
|
searches: 0,
|
||||||
const [active, setActive] = createSignal(false)
|
lists: 0,
|
||||||
const [reducedMotion, setReducedMotion] = createSignal(false)
|
active: false,
|
||||||
|
reducedMotion: false,
|
||||||
|
})
|
||||||
|
const reads = () => state.reads
|
||||||
|
const searches = () => state.searches
|
||||||
|
const lists = () => state.lists
|
||||||
|
const active = () => state.active
|
||||||
|
const reducedMotion = () => state.reducedMotion
|
||||||
|
|
||||||
let timeouts: ReturnType<typeof setTimeout>[] = []
|
let timeouts: ReturnType<typeof setTimeout>[] = []
|
||||||
|
|
||||||
@@ -74,10 +82,10 @@ export const Playground = {
|
|||||||
|
|
||||||
const startSim = () => {
|
const startSim = () => {
|
||||||
clearAll()
|
clearAll()
|
||||||
setReads(0)
|
setState("reads", 0)
|
||||||
setSearches(0)
|
setState("searches", 0)
|
||||||
setLists(0)
|
setState("lists", 0)
|
||||||
setActive(true)
|
setState("active", true)
|
||||||
const steps = rand(3, 10)
|
const steps = rand(3, 10)
|
||||||
let elapsed = 0
|
let elapsed = 0
|
||||||
|
|
||||||
@@ -86,27 +94,27 @@ export const Playground = {
|
|||||||
elapsed += delay
|
elapsed += delay
|
||||||
const t = setTimeout(() => {
|
const t = setTimeout(() => {
|
||||||
const pick = rand(0, 2)
|
const pick = rand(0, 2)
|
||||||
if (pick === 0) setReads((n) => n + 1)
|
if (pick === 0) setState("reads", (value) => value + 1)
|
||||||
else if (pick === 1) setSearches((n) => n + 1)
|
else if (pick === 1) setState("searches", (value) => value + 1)
|
||||||
else setLists((n) => n + 1)
|
else setState("lists", (value) => value + 1)
|
||||||
}, elapsed)
|
}, elapsed)
|
||||||
timeouts.push(t)
|
timeouts.push(t)
|
||||||
}
|
}
|
||||||
|
|
||||||
const end = setTimeout(() => setActive(false), elapsed + 100)
|
const end = setTimeout(() => setState("active", false), elapsed + 100)
|
||||||
timeouts.push(end)
|
timeouts.push(end)
|
||||||
}
|
}
|
||||||
|
|
||||||
const stopSim = () => {
|
const stopSim = () => {
|
||||||
clearAll()
|
clearAll()
|
||||||
setActive(false)
|
setState("active", false)
|
||||||
}
|
}
|
||||||
|
|
||||||
const reset = () => {
|
const reset = () => {
|
||||||
stopSim()
|
stopSim()
|
||||||
setReads(0)
|
setState("reads", 0)
|
||||||
setSearches(0)
|
setState("searches", 0)
|
||||||
setLists(0)
|
setState("lists", 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
const items = (): CountItem[] => [
|
const items = (): CountItem[] => [
|
||||||
@@ -164,19 +172,19 @@ export const Playground = {
|
|||||||
<button onClick={reset} style={btn()}>
|
<button onClick={reset} style={btn()}>
|
||||||
Reset
|
Reset
|
||||||
</button>
|
</button>
|
||||||
<button onClick={() => setReducedMotion((v) => !v)} style={smallBtn(reducedMotion())}>
|
<button onClick={() => setState("reducedMotion", (value) => !value)} style={smallBtn(reducedMotion())}>
|
||||||
{reducedMotion() ? "Motion: reduced" : "Motion: normal"}
|
{reducedMotion() ? "Motion: reduced" : "Motion: normal"}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div style={{ display: "flex", gap: "8px", "flex-wrap": "wrap" }}>
|
<div style={{ display: "flex", gap: "8px", "flex-wrap": "wrap" }}>
|
||||||
<button onClick={() => setReads((n) => n + 1)} style={smallBtn()}>
|
<button onClick={() => setState("reads", (value) => value + 1)} style={smallBtn()}>
|
||||||
+ read
|
+ read
|
||||||
</button>
|
</button>
|
||||||
<button onClick={() => setSearches((n) => n + 1)} style={smallBtn()}>
|
<button onClick={() => setState("searches", (value) => value + 1)} style={smallBtn()}>
|
||||||
+ search
|
+ search
|
||||||
</button>
|
</button>
|
||||||
<button onClick={() => setLists((n) => n + 1)} style={smallBtn()}>
|
<button onClick={() => setState("lists", (value) => value + 1)} style={smallBtn()}>
|
||||||
+ list
|
+ list
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { type ComponentProps, createMemo, createSignal, Show, splitProps } from "solid-js"
|
import { type ComponentProps, createMemo, Show, splitProps } from "solid-js"
|
||||||
|
import { createStore } from "solid-js/store"
|
||||||
import { Card, CardDescription } from "./card"
|
import { Card, CardDescription } from "./card"
|
||||||
import { Collapsible } from "./collapsible"
|
import { Collapsible } from "./collapsible"
|
||||||
import { Icon } from "./icon"
|
import { Icon } from "./icon"
|
||||||
@@ -16,8 +17,12 @@ export interface ToolErrorCardProps extends Omit<ComponentProps<typeof Card>, "c
|
|||||||
|
|
||||||
export function ToolErrorCard(props: ToolErrorCardProps) {
|
export function ToolErrorCard(props: ToolErrorCardProps) {
|
||||||
const i18n = useI18n()
|
const i18n = useI18n()
|
||||||
const [open, setOpen] = createSignal(props.defaultOpen ?? false)
|
const [state, setState] = createStore({
|
||||||
const [copied, setCopied] = createSignal(false)
|
open: props.defaultOpen ?? false,
|
||||||
|
copied: false,
|
||||||
|
})
|
||||||
|
const open = () => state.open
|
||||||
|
const copied = () => state.copied
|
||||||
const [split, rest] = splitProps(props, ["tool", "error", "defaultOpen", "subtitle", "href"])
|
const [split, rest] = splitProps(props, ["tool", "error", "defaultOpen", "subtitle", "href"])
|
||||||
const name = createMemo(() => {
|
const name = createMemo(() => {
|
||||||
const map: Record<string, string> = {
|
const map: Record<string, string> = {
|
||||||
@@ -65,13 +70,18 @@ export function ToolErrorCard(props: ToolErrorCardProps) {
|
|||||||
const text = cleaned()
|
const text = cleaned()
|
||||||
if (!text) return
|
if (!text) return
|
||||||
await navigator.clipboard.writeText(text)
|
await navigator.clipboard.writeText(text)
|
||||||
setCopied(true)
|
setState("copied", true)
|
||||||
setTimeout(() => setCopied(false), 2000)
|
setTimeout(() => setState("copied", false), 2000)
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card {...rest} data-kind="tool-error-card" data-open={open() ? "true" : "false"} variant="error">
|
<Card {...rest} data-kind="tool-error-card" data-open={open() ? "true" : "false"} variant="error">
|
||||||
<Collapsible class="tool-collapsible" data-open={open() ? "true" : "false"} open={open()} onOpenChange={setOpen}>
|
<Collapsible
|
||||||
|
class="tool-collapsible"
|
||||||
|
data-open={open() ? "true" : "false"}
|
||||||
|
open={open()}
|
||||||
|
onOpenChange={(value) => setState("open", value)}
|
||||||
|
>
|
||||||
<Collapsible.Trigger>
|
<Collapsible.Trigger>
|
||||||
<div data-component="tool-trigger">
|
<div data-component="tool-trigger">
|
||||||
<div data-slot="basic-tool-tool-trigger-content">
|
<div data-slot="basic-tool-tool-trigger-content">
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { Show, createEffect, createMemo, createSignal, on, onCleanup, onMount } from "solid-js"
|
import { Show, createEffect, createMemo, on, onCleanup, onMount } from "solid-js"
|
||||||
|
import { createStore } from "solid-js/store"
|
||||||
import { TextShimmer } from "./text-shimmer"
|
import { TextShimmer } from "./text-shimmer"
|
||||||
|
|
||||||
function common(active: string, done: string) {
|
function common(active: string, done: string) {
|
||||||
@@ -35,8 +36,12 @@ export function ToolStatusTitle(props: {
|
|||||||
const activeTail = createMemo(() => (suffix() ? split().active : props.activeText))
|
const activeTail = createMemo(() => (suffix() ? split().active : props.activeText))
|
||||||
const doneTail = createMemo(() => (suffix() ? split().done : props.doneText))
|
const doneTail = createMemo(() => (suffix() ? split().done : props.doneText))
|
||||||
|
|
||||||
const [width, setWidth] = createSignal("auto")
|
const [state, setState] = createStore({
|
||||||
const [ready, setReady] = createSignal(false)
|
width: "auto",
|
||||||
|
ready: false,
|
||||||
|
})
|
||||||
|
const width = () => state.width
|
||||||
|
const ready = () => state.ready
|
||||||
let activeRef: HTMLSpanElement | undefined
|
let activeRef: HTMLSpanElement | undefined
|
||||||
let doneRef: HTMLSpanElement | undefined
|
let doneRef: HTMLSpanElement | undefined
|
||||||
let frame: number | undefined
|
let frame: number | undefined
|
||||||
@@ -45,7 +50,7 @@ export function ToolStatusTitle(props: {
|
|||||||
const measure = () => {
|
const measure = () => {
|
||||||
const target = props.active ? activeRef : doneRef
|
const target = props.active ? activeRef : doneRef
|
||||||
const px = contentWidth(target)
|
const px = contentWidth(target)
|
||||||
if (px > 0) setWidth(`${px}px`)
|
if (px > 0) setState("width", `${px}px`)
|
||||||
}
|
}
|
||||||
|
|
||||||
const schedule = () => {
|
const schedule = () => {
|
||||||
@@ -62,13 +67,13 @@ export function ToolStatusTitle(props: {
|
|||||||
|
|
||||||
const finish = () => {
|
const finish = () => {
|
||||||
if (typeof requestAnimationFrame !== "function") {
|
if (typeof requestAnimationFrame !== "function") {
|
||||||
setReady(true)
|
setState("ready", true)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (readyFrame !== undefined) cancelAnimationFrame(readyFrame)
|
if (readyFrame !== undefined) cancelAnimationFrame(readyFrame)
|
||||||
readyFrame = requestAnimationFrame(() => {
|
readyFrame = requestAnimationFrame(() => {
|
||||||
readyFrame = undefined
|
readyFrame = undefined
|
||||||
setReady(true)
|
setState("ready", true)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { createEffect, createSignal, onCleanup, onMount } from "solid-js"
|
import { createEffect, onCleanup, onMount } from "solid-js"
|
||||||
|
import { createStore } from "solid-js/store"
|
||||||
|
|
||||||
export type FindHost = {
|
export type FindHost = {
|
||||||
element: () => HTMLElement | undefined
|
element: () => HTMLElement | undefined
|
||||||
@@ -107,11 +108,18 @@ export function createFileFind(opts: CreateFileFindOptions) {
|
|||||||
let mode: "highlights" | "overlay" = "overlay"
|
let mode: "highlights" | "overlay" = "overlay"
|
||||||
let hits: Range[] = []
|
let hits: Range[] = []
|
||||||
|
|
||||||
const [open, setOpen] = createSignal(false)
|
const [state, setState] = createStore({
|
||||||
const [query, setQuery] = createSignal("")
|
open: false,
|
||||||
const [index, setIndex] = createSignal(0)
|
query: "",
|
||||||
const [count, setCount] = createSignal(0)
|
index: 0,
|
||||||
const [pos, setPos] = createSignal({ top: 8, right: 8 })
|
count: 0,
|
||||||
|
pos: { top: 8, right: 8 },
|
||||||
|
})
|
||||||
|
const open = () => state.open
|
||||||
|
const query = () => state.query
|
||||||
|
const index = () => state.index
|
||||||
|
const count = () => state.count
|
||||||
|
const pos = () => state.pos
|
||||||
|
|
||||||
const clearOverlayScroll = () => {
|
const clearOverlayScroll = () => {
|
||||||
for (const el of overlayScroll) el.removeEventListener("scroll", scheduleOverlay)
|
for (const el of overlayScroll) el.removeEventListener("scroll", scheduleOverlay)
|
||||||
@@ -200,8 +208,8 @@ export function createFileFind(opts: CreateFileFindOptions) {
|
|||||||
clearOverlay()
|
clearOverlay()
|
||||||
clearOverlayScroll()
|
clearOverlayScroll()
|
||||||
hits = []
|
hits = []
|
||||||
setCount(0)
|
setState("count", 0)
|
||||||
setIndex(0)
|
setState("index", 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
const positionBar = () => {
|
const positionBar = () => {
|
||||||
@@ -214,7 +222,7 @@ export function createFileFind(opts: CreateFileFindOptions) {
|
|||||||
const title = parseFloat(getComputedStyle(root).getPropertyValue("--session-title-height"))
|
const title = parseFloat(getComputedStyle(root).getPropertyValue("--session-title-height"))
|
||||||
const header = Number.isNaN(title) ? 0 : title
|
const header = Number.isNaN(title) ? 0 : title
|
||||||
|
|
||||||
setPos({
|
setState("pos", {
|
||||||
top: Math.round(rect.top) + header - 4,
|
top: Math.round(rect.top) + header - 4,
|
||||||
right: Math.round(window.innerWidth - rect.right) + 8,
|
right: Math.round(window.innerWidth - rect.right) + 8,
|
||||||
})
|
})
|
||||||
@@ -318,8 +326,8 @@ export function createFileFind(opts: CreateFileFindOptions) {
|
|||||||
const currentIndex = total ? Math.min(desired, total - 1) : 0
|
const currentIndex = total ? Math.min(desired, total - 1) : 0
|
||||||
|
|
||||||
hits = ranges
|
hits = ranges
|
||||||
setCount(total)
|
setState("count", total)
|
||||||
setIndex(currentIndex)
|
setState("index", currentIndex)
|
||||||
|
|
||||||
const active = ranges[currentIndex]
|
const active = ranges[currentIndex]
|
||||||
if (mode === "highlights") {
|
if (mode === "highlights") {
|
||||||
@@ -342,8 +350,8 @@ export function createFileFind(opts: CreateFileFindOptions) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const close = () => {
|
const close = () => {
|
||||||
setOpen(false)
|
setState("open", false)
|
||||||
setQuery("")
|
setState("query", "")
|
||||||
clearFind()
|
clearFind()
|
||||||
if (current === host) current = undefined
|
if (current === host) current = undefined
|
||||||
}
|
}
|
||||||
@@ -352,7 +360,7 @@ export function createFileFind(opts: CreateFileFindOptions) {
|
|||||||
if (current && current !== host) current.close()
|
if (current && current !== host) current.close()
|
||||||
current = host
|
current = host
|
||||||
target = host
|
target = host
|
||||||
if (!open()) setOpen(true)
|
if (!open()) setState("open", true)
|
||||||
requestAnimationFrame(() => {
|
requestAnimationFrame(() => {
|
||||||
apply({ scroll: true })
|
apply({ scroll: true })
|
||||||
input?.focus()
|
input?.focus()
|
||||||
@@ -366,7 +374,7 @@ export function createFileFind(opts: CreateFileFindOptions) {
|
|||||||
if (total <= 0) return
|
if (total <= 0) return
|
||||||
|
|
||||||
const currentIndex = (index() + dir + total) % total
|
const currentIndex = (index() + dir + total) % total
|
||||||
setIndex(currentIndex)
|
setState("index", currentIndex)
|
||||||
|
|
||||||
const active = hits[currentIndex]
|
const active = hits[currentIndex]
|
||||||
if (!active) return
|
if (!active) return
|
||||||
@@ -449,8 +457,8 @@ export function createFileFind(opts: CreateFileFindOptions) {
|
|||||||
input = el
|
input = el
|
||||||
},
|
},
|
||||||
setQuery: (value: string) => {
|
setQuery: (value: string) => {
|
||||||
setQuery(value)
|
setState("query", value)
|
||||||
setIndex(0)
|
setState("index", 0)
|
||||||
apply({ reset: true, scroll: true })
|
apply({ reset: true, scroll: true })
|
||||||
},
|
},
|
||||||
focus,
|
focus,
|
||||||
|
|||||||
Reference in New Issue
Block a user