@naxodev/opencode-music-player 0.2.6 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +15 -15
- package/SUPPORT.md +1 -1
- package/dist/index.js +2257 -0
- package/dist/tui.js +1 -0
- package/package.json +28 -20
- package/artwork-placement.ts +0 -169
- package/artwork.ts +0 -240
- package/artwork.tsx +0 -246
- package/index.tsx +0 -493
- package/kitty-graphics.ts +0 -105
- package/system-media.ts +0 -593
- package/tmux-offset.ts +0 -120
- package/tui.tsx +0 -1
- package/types.ts +0 -144
- package/ui.tsx +0 -695
- package/waveform.tsx +0 -205
package/artwork.tsx
DELETED
|
@@ -1,246 +0,0 @@
|
|
|
1
|
-
/** @jsxImportSource @opentui/solid */
|
|
2
|
-
import type { BoxRenderable, CliRenderer } from "@opentui/core"
|
|
3
|
-
import { For, onCleanup, onMount } from "solid-js"
|
|
4
|
-
import type { Plugin } from "@opencode-ai/plugin/tui"
|
|
5
|
-
import type { Artwork } from "./types.ts"
|
|
6
|
-
import {
|
|
7
|
-
planNativeArtworkPlacement,
|
|
8
|
-
type NativeArtworkPlacementAction,
|
|
9
|
-
type NativeArtworkPlacementPlan,
|
|
10
|
-
type NativeArtworkState,
|
|
11
|
-
} from "./artwork-placement.ts"
|
|
12
|
-
import {
|
|
13
|
-
kittyDelete,
|
|
14
|
-
kittyDeletePlacement,
|
|
15
|
-
kittyDisplayPng,
|
|
16
|
-
kittyImageId,
|
|
17
|
-
kittyPlace,
|
|
18
|
-
writeGraphics,
|
|
19
|
-
} from "./kitty-graphics.ts"
|
|
20
|
-
import {
|
|
21
|
-
createTmuxOffsetCache,
|
|
22
|
-
resolveTerminalOffset,
|
|
23
|
-
type SlotGeometry,
|
|
24
|
-
} from "./tmux-offset.ts"
|
|
25
|
-
|
|
26
|
-
type Context = Plugin.Context
|
|
27
|
-
const tmuxOffsetCache = createTmuxOffsetCache()
|
|
28
|
-
const nativeArtworkImageId = kittyImageId("opencode-music-player:artwork")
|
|
29
|
-
type NativeArtworkRuntime = {
|
|
30
|
-
latestOwner: number
|
|
31
|
-
state: NativeArtworkState
|
|
32
|
-
identity: string
|
|
33
|
-
}
|
|
34
|
-
const defaultOwnershipScope = { latestOwner: 0 }
|
|
35
|
-
const nativeArtworkRuntimes = new WeakMap<CliRenderer, NativeArtworkRuntime>()
|
|
36
|
-
|
|
37
|
-
function nativeArtworkRuntime(renderer: CliRenderer): NativeArtworkRuntime {
|
|
38
|
-
const existing = nativeArtworkRuntimes.get(renderer)
|
|
39
|
-
if (existing) return existing
|
|
40
|
-
const created = {
|
|
41
|
-
latestOwner: 0,
|
|
42
|
-
state: { transmitted: 0, placement: null },
|
|
43
|
-
identity: "",
|
|
44
|
-
}
|
|
45
|
-
nativeArtworkRuntimes.set(renderer, created)
|
|
46
|
-
return created
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
export type NativeArtworkOwnership = { isCurrent: () => boolean }
|
|
50
|
-
|
|
51
|
-
export function claimNativeArtworkOwnership(
|
|
52
|
-
scope: { latestOwner: number } = defaultOwnershipScope,
|
|
53
|
-
): NativeArtworkOwnership {
|
|
54
|
-
const owner = ++scope.latestOwner
|
|
55
|
-
return { isCurrent: () => owner === scope.latestOwner }
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
export function cleanupNativeArtwork(
|
|
59
|
-
ownership: NativeArtworkOwnership,
|
|
60
|
-
remove: () => void,
|
|
61
|
-
): void {
|
|
62
|
-
if (ownership.isCurrent()) remove()
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
export function imageIdForArtwork(_artworkId: string): number {
|
|
66
|
-
return nativeArtworkImageId
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
export function legacyImageIdForArtwork(artworkId: string): number {
|
|
70
|
-
return kittyImageId(artworkId)
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
export function legacyImageIdForResolvedArtwork(
|
|
74
|
-
artwork: Pick<Artwork, "id" | "legacy_id">,
|
|
75
|
-
): number {
|
|
76
|
-
return legacyImageIdForArtwork(artwork.legacy_id ?? artwork.id)
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
function terminalOffset(slot: SlotGeometry | null) {
|
|
80
|
-
return resolveTerminalOffset({
|
|
81
|
-
slot,
|
|
82
|
-
cache: tmuxOffsetCache,
|
|
83
|
-
})
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
function supportsKittyGraphics(context: Context): boolean {
|
|
87
|
-
return (
|
|
88
|
-
!!context.renderer.capabilities?.kitty_graphics ||
|
|
89
|
-
process.env.TERM_PROGRAM?.toLowerCase() === "ghostty"
|
|
90
|
-
)
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
function copyState(next: NativeArtworkState): NativeArtworkState {
|
|
94
|
-
return {
|
|
95
|
-
transmitted: next.transmitted,
|
|
96
|
-
placement: next.placement ? { ...next.placement } : null,
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
export function AlbumArtwork(props: { context: Context; artwork: Artwork }) {
|
|
101
|
-
const renderer = props.context.renderer
|
|
102
|
-
const runtime = nativeArtworkRuntime(renderer)
|
|
103
|
-
const ownership = claimNativeArtworkOwnership(runtime)
|
|
104
|
-
let container: BoxRenderable | undefined
|
|
105
|
-
let paintPending = false
|
|
106
|
-
let disposed = false
|
|
107
|
-
|
|
108
|
-
const applyAction = (action: NativeArtworkPlacementAction): boolean => {
|
|
109
|
-
const renderer = props.context.renderer
|
|
110
|
-
switch (action.type) {
|
|
111
|
-
case "delete-image":
|
|
112
|
-
return writeGraphics(renderer, kittyDelete(action.imageId))
|
|
113
|
-
case "delete-placement":
|
|
114
|
-
return writeGraphics(renderer, kittyDeletePlacement(action.imageId))
|
|
115
|
-
case "transmit-and-display": {
|
|
116
|
-
const commands = kittyDisplayPng(
|
|
117
|
-
props.artwork.png_base64,
|
|
118
|
-
action.imageId,
|
|
119
|
-
action.x,
|
|
120
|
-
action.y,
|
|
121
|
-
action.width,
|
|
122
|
-
action.height,
|
|
123
|
-
)
|
|
124
|
-
return writeGraphics(renderer, commands)
|
|
125
|
-
}
|
|
126
|
-
case "place":
|
|
127
|
-
return writeGraphics(
|
|
128
|
-
renderer,
|
|
129
|
-
kittyPlace(
|
|
130
|
-
action.imageId,
|
|
131
|
-
action.placementId,
|
|
132
|
-
action.x,
|
|
133
|
-
action.y,
|
|
134
|
-
action.width,
|
|
135
|
-
action.height,
|
|
136
|
-
),
|
|
137
|
-
)
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
// Partial success must not commit: stop on first failed write and keep prior state.
|
|
142
|
-
const applyPlan = (plan: NativeArtworkPlacementPlan): boolean => {
|
|
143
|
-
for (const action of plan.actions) {
|
|
144
|
-
if (!applyAction(action)) return false
|
|
145
|
-
}
|
|
146
|
-
return true
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
const commitPlan = (plan: NativeArtworkPlacementPlan) => {
|
|
150
|
-
runtime.state = copyState(plan.nextState)
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
const paintNativeImage = () => {
|
|
154
|
-
if (!ownership.isCurrent()) return
|
|
155
|
-
if (runtime.identity !== props.artwork.id) {
|
|
156
|
-
runtime.identity = props.artwork.id
|
|
157
|
-
writeGraphics(
|
|
158
|
-
props.context.renderer,
|
|
159
|
-
kittyDelete(legacyImageIdForResolvedArtwork(props.artwork)),
|
|
160
|
-
)
|
|
161
|
-
runtime.state = { transmitted: 0, placement: null }
|
|
162
|
-
}
|
|
163
|
-
const kittySupported = supportsKittyGraphics(props.context)
|
|
164
|
-
const slotValid =
|
|
165
|
-
!!container &&
|
|
166
|
-
!container.isDestroyed &&
|
|
167
|
-
container.width >= 1 &&
|
|
168
|
-
container.height >= 1
|
|
169
|
-
|
|
170
|
-
const slot: SlotGeometry | null = container
|
|
171
|
-
? {
|
|
172
|
-
screenX: container.screenX,
|
|
173
|
-
screenY: container.screenY,
|
|
174
|
-
width: container.width,
|
|
175
|
-
height: container.height,
|
|
176
|
-
}
|
|
177
|
-
: null
|
|
178
|
-
const offset = terminalOffset(slot)
|
|
179
|
-
const imageId = imageIdForArtwork(props.artwork.id)
|
|
180
|
-
const x = slot ? slot.screenX + offset.x : 0
|
|
181
|
-
const y = slot ? slot.screenY + offset.y : 0
|
|
182
|
-
const width = slot?.width ?? 0
|
|
183
|
-
const height = slot?.height ?? 0
|
|
184
|
-
|
|
185
|
-
const plan = planNativeArtworkPlacement({
|
|
186
|
-
state: runtime.state,
|
|
187
|
-
imageId,
|
|
188
|
-
x,
|
|
189
|
-
y,
|
|
190
|
-
width,
|
|
191
|
-
height,
|
|
192
|
-
kittySupported,
|
|
193
|
-
slotValid,
|
|
194
|
-
disposed,
|
|
195
|
-
})
|
|
196
|
-
|
|
197
|
-
if (plan.actions.length === 0) return
|
|
198
|
-
if (applyPlan(plan)) commitPlan(plan)
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
const scheduleNativeImage = () => {
|
|
202
|
-
if (paintPending || disposed) return
|
|
203
|
-
paintPending = true
|
|
204
|
-
void props.context.renderer.idle().then(() => {
|
|
205
|
-
paintPending = false
|
|
206
|
-
if (!disposed && ownership.isCurrent()) paintNativeImage()
|
|
207
|
-
})
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
onMount(() => props.context.renderer.on("frame", scheduleNativeImage))
|
|
211
|
-
onCleanup(() => {
|
|
212
|
-
disposed = true
|
|
213
|
-
props.context.renderer.off("frame", scheduleNativeImage)
|
|
214
|
-
// A replacement mount claims ownership before the next task runs.
|
|
215
|
-
setTimeout(() => {
|
|
216
|
-
cleanupNativeArtwork(ownership, () => {
|
|
217
|
-
if (!renderer.isDestroyed)
|
|
218
|
-
writeGraphics(renderer, kittyDelete(nativeArtworkImageId))
|
|
219
|
-
runtime.state = { transmitted: 0, placement: null }
|
|
220
|
-
runtime.identity = ""
|
|
221
|
-
})
|
|
222
|
-
}, 0)
|
|
223
|
-
})
|
|
224
|
-
|
|
225
|
-
return (
|
|
226
|
-
<box
|
|
227
|
-
ref={(value) => (container = value)}
|
|
228
|
-
width={24}
|
|
229
|
-
height={12}
|
|
230
|
-
flexDirection="column"
|
|
231
|
-
overflow="hidden"
|
|
232
|
-
>
|
|
233
|
-
<For each={props.artwork.cells}>
|
|
234
|
-
{(row) => (
|
|
235
|
-
<text>
|
|
236
|
-
<For each={row}>
|
|
237
|
-
{(cell) => (
|
|
238
|
-
<span style={{ fg: cell.upper, bg: cell.lower }}>▀</span>
|
|
239
|
-
)}
|
|
240
|
-
</For>
|
|
241
|
-
</text>
|
|
242
|
-
)}
|
|
243
|
-
</For>
|
|
244
|
-
</box>
|
|
245
|
-
)
|
|
246
|
-
}
|