@naxodev/opencode-music-player 0.5.4 → 0.5.5

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.
Files changed (2) hide show
  1. package/dist/index.js +103 -22
  2. package/package.json +2 -2
package/dist/index.js CHANGED
@@ -1030,6 +1030,10 @@ function AlbumArtwork(props) {
1030
1030
  let container;
1031
1031
  let paintPending = false;
1032
1032
  let disposed = false;
1033
+ let renderedFrameId = -1;
1034
+ let renderedSlot = null;
1035
+ let placementInvalidated = false;
1036
+ let awaitingResizeGeometry = false;
1033
1037
  const [nativeVisible, setNativeVisible] = createSignal(false);
1034
1038
  const imageDimensions = createMemo(() => pngDimensions(props.artwork.png_base64));
1035
1039
  const applyAction = (action) => {
@@ -1057,25 +1061,69 @@ function AlbumArtwork(props) {
1057
1061
  const commitPlan = (plan) => {
1058
1062
  runtime.state = copyState(plan.nextState);
1059
1063
  };
1064
+ const clearInvalidatedPlacement = () => {
1065
+ if (!placementInvalidated)
1066
+ return true;
1067
+ if (runtime.state.transmitted !== 0 && !writeGraphics(renderer, kittyDeletePlacement(runtime.state.transmitted)))
1068
+ return false;
1069
+ runtime.state = {
1070
+ ...runtime.state,
1071
+ placement: null
1072
+ };
1073
+ placementInvalidated = false;
1074
+ return true;
1075
+ };
1076
+ const clearPlacement = () => {
1077
+ if (runtime.state.transmitted !== 0 && runtime.state.placement !== null && !writeGraphics(renderer, kittyDeletePlacement(runtime.state.transmitted)))
1078
+ return false;
1079
+ runtime.state = {
1080
+ ...runtime.state,
1081
+ placement: null
1082
+ };
1083
+ renderedSlot = null;
1084
+ setNativeVisible(false);
1085
+ return true;
1086
+ };
1087
+ const fitsAvailableSpace = (node) => {
1088
+ for (let parent = node.parent;parent; parent = parent.parent) {
1089
+ if (parent.width < node.width || parent.height < node.height)
1090
+ return false;
1091
+ if (parent.overflow === "visible")
1092
+ continue;
1093
+ if (node.screenX < parent.screenX || node.screenY < parent.screenY || node.screenX + node.width > parent.screenX + parent.width || node.screenY + node.height > parent.screenY + parent.height)
1094
+ return false;
1095
+ }
1096
+ return true;
1097
+ };
1098
+ const fitsViewport = (slot) => slot.screenX >= 0 && slot.screenY >= 0 && slot.screenX + slot.width <= renderer.terminalWidth && slot.screenY + slot.height <= renderer.terminalHeight;
1099
+ const invalidateForResize = () => {
1100
+ if (disposed || !ownership.isCurrent())
1101
+ return;
1102
+ tmuxOffsetCache.offset = null;
1103
+ tmuxOffsetCache.slot = null;
1104
+ renderedFrameId = -1;
1105
+ renderedSlot = null;
1106
+ placementInvalidated = true;
1107
+ awaitingResizeGeometry = true;
1108
+ clearInvalidatedPlacement();
1109
+ setNativeVisible(false);
1110
+ };
1060
1111
  const paintNativeImage = () => {
1061
1112
  if (!ownership.isCurrent())
1062
1113
  return;
1063
- if (runtime.identity !== props.artwork.id) {
1064
- runtime.identity = props.artwork.id;
1065
- writeGraphics(props.context.renderer, kittyDelete(legacyImageIdForResolvedArtwork(props.artwork)));
1066
- runtime.state = {
1067
- transmitted: 0,
1068
- placement: null
1069
- };
1070
- }
1114
+ if (!clearInvalidatedPlacement())
1115
+ return;
1071
1116
  const kittySupported = supportsKittyGraphics(props.context);
1117
+ if (container && !fitsAvailableSpace(container)) {
1118
+ clearPlacement();
1119
+ return;
1120
+ }
1072
1121
  const slotValid = !!container && !container.isDestroyed && container.width >= 1 && container.height >= 1;
1073
- const slot = container ? {
1074
- screenX: container.screenX,
1075
- screenY: container.screenY,
1076
- width: container.width,
1077
- height: container.height
1078
- } : null;
1122
+ const slot = renderedSlot;
1123
+ if (slot && !fitsViewport(slot)) {
1124
+ clearPlacement();
1125
+ return;
1126
+ }
1079
1127
  const offset = terminalOffset(slot);
1080
1128
  const imageId = imageIdForArtwork(props.artwork.id);
1081
1129
  const x = slot ? slot.screenX + offset.x : 0;
@@ -1091,6 +1139,20 @@ function AlbumArtwork(props) {
1091
1139
  width: Math.floor(resolution.width / renderer.terminalWidth),
1092
1140
  height: Math.floor(resolution.height / renderer.terminalHeight)
1093
1141
  }) : null;
1142
+ if (awaitingResizeGeometry && geometry === null && kittySupported)
1143
+ return;
1144
+ awaitingResizeGeometry = false;
1145
+ if (runtime.identity !== props.artwork.id) {
1146
+ if (!clearPlacement())
1147
+ return;
1148
+ if (!writeGraphics(renderer, kittyDelete(legacyImageIdForResolvedArtwork(props.artwork))))
1149
+ return;
1150
+ runtime.identity = props.artwork.id;
1151
+ runtime.state = {
1152
+ transmitted: 0,
1153
+ placement: null
1154
+ };
1155
+ }
1094
1156
  const plan = planNativeArtworkPlacement({
1095
1157
  state: runtime.state,
1096
1158
  imageId,
@@ -1118,20 +1180,28 @@ function AlbumArtwork(props) {
1118
1180
  if (paintPending || disposed)
1119
1181
  return;
1120
1182
  paintPending = true;
1121
- props.context.renderer.idle().then(() => {
1122
- paintPending = false;
1123
- if (!disposed && ownership.isCurrent())
1124
- paintNativeImage();
1125
- }).catch((error) => {
1183
+ queueMicrotask(() => {
1126
1184
  paintPending = false;
1127
- if (!disposed && ownership.isCurrent())
1185
+ if (disposed || !ownership.isCurrent())
1186
+ return;
1187
+ try {
1188
+ if (renderedFrameId !== renderer.frameId)
1189
+ clearPlacement();
1190
+ else
1191
+ paintNativeImage();
1192
+ } catch (error) {
1128
1193
  console.error("Failed to paint music artwork", error);
1194
+ }
1129
1195
  });
1130
1196
  };
1131
- onMount(() => props.context.renderer.on("frame", scheduleNativeImage));
1197
+ onMount(() => {
1198
+ renderer.on("frame", scheduleNativeImage);
1199
+ renderer.on("resize", invalidateForResize);
1200
+ });
1132
1201
  onCleanup(() => {
1133
1202
  disposed = true;
1134
- props.context.renderer.off("frame", scheduleNativeImage);
1203
+ renderer.off("frame", scheduleNativeImage);
1204
+ renderer.off("resize", invalidateForResize);
1135
1205
  setTimeout(() => {
1136
1206
  cleanupNativeArtwork(ownership, () => {
1137
1207
  if (!renderer.isDestroyed)
@@ -1147,6 +1217,17 @@ function AlbumArtwork(props) {
1147
1217
  return (() => {
1148
1218
  var _el$ = _$createElement("box");
1149
1219
  _$use((value) => container = value, _el$);
1220
+ _$setProp(_el$, "renderAfter", () => {
1221
+ if (!container)
1222
+ return;
1223
+ renderedFrameId = renderer.frameId;
1224
+ renderedSlot = {
1225
+ screenX: container.screenX,
1226
+ screenY: container.screenY,
1227
+ width: container.width,
1228
+ height: container.height
1229
+ };
1230
+ });
1150
1231
  _$setProp(_el$, "width", 24);
1151
1232
  _$setProp(_el$, "height", 12);
1152
1233
  _$setProp(_el$, "flexDirection", "column");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@naxodev/opencode-music-player",
3
- "version": "0.5.4",
3
+ "version": "0.5.5",
4
4
  "description": "macOS system Now Playing controls for the OpenCode 2 TUI",
5
5
  "license": "MIT",
6
6
  "author": {
@@ -60,7 +60,7 @@
60
60
  "validate": "bun run format:check && bun run check && bun run test && bun run pack:check"
61
61
  },
62
62
  "dependencies": {
63
- "@naxodev/music-core": "^0.2.1",
63
+ "@naxodev/music-core": "^0.2.2",
64
64
  "pngjs": "^7.0.0"
65
65
  },
66
66
  "peerDependencies": {